Por que as variáveis ​​Java ThreadLocal devem ser estáticas


101

Eu estava lendo o JavaDoc para Threadlocal aqui

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

e diz "Instâncias ThreadLocal são tipicamente campos estáticos privados em classes que desejam associar o estado a um thread (por exemplo, um ID de usuário ou ID de transação)."

Mas minha pergunta é por que eles escolheram torná-lo estático (normalmente) - torna as coisas um pouco confusas ter um estado "por thread", mas os campos são estáticos?

Respostas:


131

Porque se fosse um campo de nível de instância, seria na verdade "Por Thread - Por Instância", não apenas um "Por Thread" garantido. Normalmente, essa não é a semântica que você está procurando.

Normalmente, ele contém algo como objetos que têm como escopo uma Conversa de usuário, Solicitação da Web, etc. Você não quer que eles também tenham como escopo a instância da classe.
Uma solicitação da web => uma sessão de persistência.
Nenhuma solicitação da web => uma sessão de persistência por objeto.


2
Gosto desta explicação porque mostra como o ThreadLocal deve ser usado
kellyfj

4
Por thread por instância pode ser uma semântica útil, mas a maioria dos usos para esse padrão envolveria tantos objetos que seria melhor usar um ThreadLocalpara manter uma referência a um conjunto de hash que mapeia objetos para instâncias por thread.
supercat de

@opcional Significa apenas que cada instância do não-estático ThreadLocalmanteria seus próprios dados de segmento local, mesmo se essas ThreadLocalinstâncias existissem no mesmo segmento. Não é necessariamente errado fazer isso - acho que pode ser o padrão menos popular dos dois
geg

17

Torne-o estático ou, se estiver tentando evitar quaisquer campos estáticos em sua classe - torne a própria classe um singleton e então você pode usar com segurança o ThreadLocal em nível de instância, contanto que tenha esse singleton disponível globalmente.



3

O motivo é que as variáveis ​​são acessadas por meio de um ponteiro associado ao thread. Eles agem como variáveis ​​globais com escopo de thread, portanto, estático é o ajuste mais próximo. Esta é a maneira que você obtém o estado local do thread em coisas como pthreads, então isso pode ser apenas um acidente de história e implementação.


1

Um uso para um threadlocal em uma instância por thread é se você deseja que algo seja visível em todos os métodos de um objeto e que seja seguro para thread sem sincronizar o acesso a ele, como faria para um campo comum.


1

Consulte este , este dar uma melhor compreensão.

Em suma, o ThreadLocalobjeto funciona como um mapa de valor-chave. Quando o ThreadLocal get/setmétodo invocar o thread , ele irá recuperar / armazenar o objeto do thread na chave do mapa e o valor no valor do mapa. É por isso que diferentes threads têm diferentes cópias de valor (que você deseja armazenar localmente), porque ele reside em diferentes entradas do mapa.

É por isso que você só precisa de um mapa para manter todos os valores. Embora não seja necessário, você pode ter vários mapas (sem declarar estático) para manter cada objeto de thread também, o que, é totalmente redundante, por isso a variável estática é preferida.


-1

static final ThreadLocal variáveis ​​são thread-safe.

statictorna a variável ThreadLocal disponível em várias classes para o respectivo thread apenas. é uma espécie de descaracterização de variável global das respectivas variáveis ​​locais de thread em várias classes.

Podemos verificar a segurança deste thread com o seguinte exemplo de código.

  • CurrentUser - armazena o ID do usuário atual em ThreadLocal
  • TestService- Serviço simples com método - getUser()para buscar o usuário atual de CurrentUser.
  • TestThread - esta classe é usada para criar vários threads e definir IDs de usuário simultaneamente

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Execute a classe principal TestThread. Resultado -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Resumo da análise

  1. O thread "principal" inicia e define o usuário atual como "62", paralelamente o thread "ForkJoinPool.commonPool-worker-2" é iniciado e define o usuário atual como "31", paralelamente o thread "ForkJoinPool.commonPool-worker-3" é iniciado e definido como atual usuário como "81", paralelamente o thread "ForkJoinPool.commonPool-worker-1" inicia e define o usuário atual como "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. Todos esses tópicos acima irão dormir por 3 segundos
  3. maina execução termina e imprime o usuário atual como "62", a ForkJoinPool.commonPool-worker-1execução paralela termina e imprime o usuário atual como "87", a ForkJoinPool.commonPool-worker-2execução paralela termina e imprime o usuário atual como "31", a ForkJoinPool.commonPool-worker-3execução paralela termina e imprime o usuário atual como "81"

Inferência

Threads simultâneos são capazes de recuperar IDs de usuário corretos, mesmo que tenham sido declarados como "ThreadLocal final estático"

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.