Quando e como devo usar uma variável ThreadLocal?

877

Quando devo usar uma ThreadLocalvariável?

Como isso é usado?

Hearen
fonte
11
Se você estiver usando o ThreadLocal, nunca escreva um wrapper sobre ele !!! Todo desenvolvedor que deseja usar a variável deve saber que é 'ThreadLocal'
Não é um bug
2
@ Nototug Se você é explícito, pode nomear sua variável para que esteja lidando com um valor ThreadLocal, por exemplo RequestUtils.getCurrentRequestThreadLocal(). Não dizendo que isso é muito elegante, mas isso decorre do fato de o ThreadLocal em si não ser muito elegante na maioria dos casos.
whirlwin
@Sasha Pode o ThreadLocal ser usado para rastrear a execução loja
gaurav

Respostas:

864

Um uso possível (e comum) é quando você tem algum objeto que não é seguro para threads, mas deseja evitar sincronizar o acesso a esse objeto (estou olhando para você, SimpleDateFormat ). Em vez disso, dê a cada thread sua própria instância do objeto.

Por exemplo:

public class Foo
{
    // SimpleDateFormat is not thread-safe, so give one to each thread
    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyyMMdd HHmm");
        }
    };

    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }
}

Documentação .

pensar demais
fonte
160
Outra alternativa à sincronização ou ao threadlocal é tornar a variável uma variável local. Vars locais são sempre thread-safe. Suponho que é uma prática inadequada tornar os DateFormats locais porque eles são caros de criar, mas nunca vi métricas sólidas sobre esse tópico.
Julien Chastang #
18
Este é um preço alto a pagar por hackers SimpleDateFormat. Talvez seja melhor usar uma alternativa segura para threads . Se você concorda que singletons são ruins , ThreadLocalé ainda pior.
Alexander Ryzhov
4
@overthink Existe alguma razão para declarar ThreadLocal estático e final, quero dizer desempenho ou algo assim?
Sachin Gorade
4
O método ThreadLocal.get () chamará ThreadLocal.initialValue () (uma vez) para cada segmento, o que significa que um objeto SimpleDateFormat é criado para cada segmento. Não é melhor usar o SimpleDateFormat como variáveis ​​locais (já que não precisamos lidar com problemas de coleta de lixo)?
sudeepdino008
3
Apenas tome cuidado para não usar a inicialização entre chaves para seu SimpleDateFormat, pois isso criaria uma classe anônima para que seu carregador de classes não pudesse ser coletado como lixo. vazamento de memória: retornar novo SimpleDateFormat () {{applyPattern ("aaaaMMdd HHmm")}};
user1944408
427

Como a ThreadLocalé uma referência aos dados em um determinado Thread, você pode acabar com vazamentos de carregamento de classe ao usar ThreadLocals em servidores de aplicativos usando conjuntos de encadeamentos. Você precisa ter muito cuidado com a limpeza de quaisquer ThreadLocals você get()ou set()usando o ThreadLocal's remove()método.

Se você não fizer a limpeza quando terminar, quaisquer referências que ele contenha para classes carregadas como parte de um webapp implantado permanecerão no heap permanente e nunca serão coletadas como lixo. A reimplantação / desimplantação do aplicativo da web não limpará a Threadreferência de cada uma das classes do aplicativo da web, uma vez que Threadnão é algo de propriedade do seu aplicativo da web. Cada implantação sucessiva criará uma nova instância da classe que nunca será coletada como lixo.

Você terminará com exceções de falta de memória devido a java.lang.OutOfMemoryError: PermGen spacee após alguns estudos no Google provavelmente aumentará em -XX:MaxPermSizevez de corrigir o erro.

Se você enfrentar esses problemas, poderá determinar qual thread e classe está mantendo essas referências usando o Memory Analyzer do Eclipse e / ou seguindo o guia e acompanhamento de Frank Kieviet .

Atualização: redescobri a entrada do blog de Alex Vasseur que me ajudou a rastrear alguns ThreadLocalproblemas que eu estava tendo.

Phil M
fonte
11
Alex Vasseur mudou seu blog. Aqui está um link atual para o artigo sobre vazamento de memória.
Kenster
12
O fio que liga Julien para se mudou para que parece, e é bem vale a pena ler ...
Donal Fellows
28
Essa é uma quantidade enorme de votos positivos para uma resposta que, embora informativa, não responde de maneira alguma à pergunta.
22614 Robin
8
Agora que o PermGen é morto pelo Java 8, isso muda essa resposta de alguma maneira?
Máquina de lavar malvada
13
@ Robin: Eu discordo. A questão é sobre como usar o ThreadLocal corretamente, para obter um entendimento completo de qualquer conceito (ThreadLocal aqui), também é importante entender como não usá-lo e os riscos de usá-lo descuidadamente, e é disso que se trata a resposta de Phil. Fico feliz que ele tenha abordado esse ponto que não é abordado em nenhuma outra resposta. Todos esses votos são merecidos. Acredito que o SO deve construir uma compreensão dos conceitos, em vez de apenas ser um site de controle de qualidade.
Saurabh Patil
166

Muitas estruturas usam ThreadLocals para manter algum contexto relacionado ao segmento atual. Por exemplo, quando a transação atual é armazenada em um ThreadLocal, você não precisa transmiti-la como parâmetro em todas as chamadas de métodos, caso alguém da pilha precise acessar a mesma. Os aplicativos da Web podem armazenar informações sobre a solicitação e sessão atuais em um ThreadLocal, para que o aplicativo tenha acesso fácil a eles. Com o Guice, você pode usar o ThreadLocals ao implementar escopos personalizados para os objetos injetados (o padrão do Guice escopos de servlet provavelmente os usam também).

ThreadLocals são um tipo de variáveis ​​globais (embora um pouco menos ruins porque estão restritas a um thread), portanto, você deve ter cuidado ao usá-las para evitar efeitos colaterais indesejados e vazamentos de memória. Crie suas APIs para que os valores ThreadLocal sempre sejam limpos automaticamente quando não forem mais necessários e que o uso incorreto da API não seja possível (por exemplo, assim ). O ThreadLocals pode ser usado para tornar o código mais limpo e, em alguns casos raros, é a única maneira de fazer alguma coisa funcionar (meu projeto atual teve dois desses casos; eles estão documentados aqui em "Campos estáticos e variáveis ​​globais").

Esko Luontola
fonte
4
É exatamente assim que a estrutura exPOJO (www.expojo.com) permite acesso ao ORM Session / PersistenceManager sem precisar da sobrecarga de anotações e injeção. É como 'injeção de thread' em vez de 'injeção de objeto'. Ele fornece acesso a dependências sem a necessidade (e a sobrecarga) de tê-las incorporadas em todos os objetos que possam precisar dessas dependências. É incrível como 'peso leve' você pode fazer um quadro DI quando você usa injeção de rosca em vez de DI clássico (por exemplo, Primavera etc,.)
Volksman
27
Por que tive que rolar até agora para encontrar esta resposta essencial !?
9608 Jeremy Stein
1
@Esko, em seu projeto, o uso de ThreadLocal em hashcode e igual é desnecessário. Uma abordagem melhor é criar ou passar uma referência a uma função de código de hash / igual sempre que necessário. Dessa forma, você pode evitar completamente a necessidade do hack ThreadLocal.
Pacerier 19/09/17
1
Esta é a melhor explicação para o uso do TL.
Dexter #
52

Em Java, se você possui um dado que pode variar por segmento, suas opções são passá-lo a todos os métodos que precisam (ou podem precisar) dele ou associar o dado ao segmento. Passar o dado por todo o lado pode ser viável se todos os seus métodos já precisarem passar por uma variável "contexto" comum.

Se não for esse o caso, convém não desorganizar as assinaturas do método com um parâmetro adicional. Em um mundo não encadeado, você pode resolver o problema com o equivalente em Java de uma variável global. Em uma palavra encadeada, o equivalente a uma variável global é uma variável local de encadeamento.

user100464
fonte
13
Portanto, você deve evitar os locais de threads da mesma maneira que evita os globais. Não posso aceitar que não há problema em criar globais (threads locais) em vez de transmitir valores, as pessoas não gostam, porque muitas vezes revela problemas de arquitetura que não desejam corrigir.
Juan Mendes
10
Talvez ... Pode ser útil, no entanto, se você tiver uma enorme base de código existente na qual precisará adicionar um novo dado que deve ser distribuído por toda parte , por exemplo, um contexto de sessão, transação db, usuário conectado, etc. .
Cornel Masson
6
Parabéns por usar dados em vez de dados.
profunda
Isso me deixou curioso. 'datum' is the singular form and 'data' is the plural form.
Siddhartha
23

Há um exemplo muito bom no livro Java Concurrency in Practice . Onde o autor ( Joshua Bloch ) explica como o confinamento do Thread é uma das maneiras mais simples de obter segurança de thread e o ThreadLocal é o meio mais formal de manter o confinamento do thread. No final, ele também explica como as pessoas podem abusar dele usando-o como variáveis ​​globais.

Copiei o texto do livro mencionado, mas o código 3.10 está ausente, pois não é muito importante entender onde o ThreadLocal deve ser usado.

As variáveis ​​locais de encadeamento são frequentemente usadas para impedir o compartilhamento em projetos baseados em Singletons mutáveis ​​ou variáveis ​​globais. Por exemplo, um aplicativo de thread único pode manter uma conexão de banco de dados global inicializada na inicialização para evitar a necessidade de passar uma Conexão para todos os métodos. Como as conexões JDBC podem não ser seguras para threads, um aplicativo multithread que usa uma conexão global sem coordenação adicional também não é seguro para threads. Usando um ThreadLocal para armazenar a conexão JDBC, como no ConnectionHolder na Listagem 3.10, cada encadeamento terá sua própria conexão.

O ThreadLocal é amplamente usado na implementação de estruturas de aplicativos. Por exemplo, os contêineres J2EE associam um contexto de transação a um encadeamento em execução pela duração de uma chamada EJB. Isso é facilmente implementado usando um Thread-Local estático que mantém o contexto da transação: quando o código da estrutura precisa determinar qual transação está em execução no momento, ele busca o contexto da transação neste ThreadLocal. Isso é conveniente, pois reduz a necessidade de passar informações de contexto de execução para todos os métodos, mas associa qualquer código que use esse mecanismo à estrutura.

É fácil abusar do ThreadLocal, tratando sua propriedade de confinamento de threads como uma licença para usar variáveis ​​globais ou como um meio de criar argumentos de método "ocultos". Como as variáveis ​​globais, as variáveis ​​locais do encadeamento podem prejudicar a reutilização e introduzir acoplamentos ocultos entre as classes e, portanto, devem ser usadas com cuidado.

Rakesh Chauhan
fonte
18

Essencialmente, quando você precisa que o valor de uma variável dependa do encadeamento atual e não seja conveniente anexar o valor ao encadeamento de alguma outra maneira (por exemplo, encadeamento de subclassificação).

Um caso típico é onde alguma outra estrutura criou o encadeamento em que seu código está sendo executado, por exemplo, um contêiner de servlet ou onde faz mais sentido usar ThreadLocal porque sua variável está "em seu lugar lógico" (em vez de uma variável pendurado em uma subclasse Thread ou em outro mapa de hash).

No meu site, tenho mais algumas discussões e exemplos de quando usar o ThreadLocal que também podem ser interessantes.

Algumas pessoas defendem o uso do ThreadLocal como uma maneira de anexar um "ID do thread" a cada thread em certos algoritmos simultâneos em que você precisa de um número de thread (consulte, por exemplo, Herlihy & Shavit). Nesses casos, verifique se você está realmente recebendo um benefício!

Neil Coffey
fonte
Pode o ThreadLocal ser usado para rastrear a execução loja
gaurav
13
  1. O ThreadLocal em Java foi introduzido no JDK 1.2, mas mais tarde foi gerado no JDK 1.5 para introduzir segurança de tipo na variável ThreadLocal.

  2. O ThreadLocal pode ser associado ao escopo do Thread, todo o código executado pelo Thread tem acesso às variáveis ​​ThreadLocal, mas dois threads não podem ver a variável ThreadLocal um do outro.

  3. Cada encadeamento contém uma cópia exclusiva da variável ThreadLocal que se torna elegível para a coleta de lixo após o encadeamento terminar ou morrer, normalmente ou devido a qualquer exceção, considerando que a variável ThreadLocal não possui outras referências ativas.

  4. As variáveis ​​ThreadLocal em Java geralmente são campos estáticos privados em Classes e mantêm seu estado dentro do Thread.

Leia mais: ThreadLocal em Java - programa e tutorial de exemplo

Abhijit Gaikwad
fonte
Pode o ThreadLocal ser usado para rastrear a execução loja
gaurav
11

A documentação diz muito bem: "cada thread que acessa [uma variável local de thread] (por meio de seu método get ou set) possui sua própria cópia inicializada da variável, independente".

Você usa um quando cada segmento deve ter sua própria cópia de algo. Por padrão, os dados são compartilhados entre os threads.

RichieHindle
fonte
18
Por padrão, objetos estáticos, ou objetos explicitamente transmitidos entre threads, nos quais os dois threads possuem uma referência ao mesmo objeto, são compartilhados. Objetos declarados localmente em um encadeamento não são compartilhados (são locais na pilha do encadeamento). Só queria esclarecer isso.
Ian Varley
@IanVarley: Obrigado por apontar isso. Portanto, se tivermos uma variável declarada e usada na classe MyThread, precisamos do ThreadLocal nesse caso? Exemplo: a classe MyThread estende o Thread {String var1 ;, int var2; } nesse caso, var1 e var2 farão parte da própria pilha do encadeamento e não serão compartilhados. Por isso, exigimos ThreadLocal nesse caso? Eu posso estar totalmente errado na minha compreensão, por favor, guie.
Jayesh
4
Se cada encadeamento deseja ter sua própria cópia de algo, por que não pode simplesmente ser declarado local (que é sempre encadeado)?
sudeepdino008
@ sudeepdino008 você nem sempre tem o controle da criação desses tópicos (estruturas webapp, ThreadPool bibliotecas)
JMess
10

O servidor Webapp pode manter um conjunto de encadeamentos e uma ThreadLocalvar deve ser removida antes da resposta ao cliente, portanto, o encadeamento atual pode ser reutilizado pela próxima solicitação.

znlyj
fonte
8

Dois casos de uso em que a variável threadlocal pode ser usada -
1- Quando temos um requisito para associar o estado a um encadeamento (por exemplo, um ID do usuário ou Transaction). Isso geralmente acontece com um aplicativo da Web que toda solicitação que vai para um servlet tem um transactionID exclusivo associado a ele.

// This class will provide a thread local variable which
// will provide a unique ID for each thread
class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);

    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
        ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();});

    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
        return threadId.get();
    }
}

Observe que aqui o método withInitial é implementado usando a expressão lambda.
2- Outro caso de uso é quando queremos ter uma instância segura de thread e não queremos usar a sincronização, pois o custo de desempenho com a sincronização é maior. Um desses casos é quando SimpleDateFormat é usado. Como o SimpleDateFormat não é seguro para threads, precisamos fornecer um mecanismo para torná-lo seguro.

public class ThreadLocalDemo1 implements Runnable {
    // threadlocal variable is created
    private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue(){
            System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() );
            return new SimpleDateFormat("dd/MM/yyyy");
        }
    };

    public static void main(String[] args) {
        ThreadLocalDemo1 td = new ThreadLocalDemo1();
        // Two threads are created
        Thread t1 = new Thread(td, "Thread-1");
        Thread t2 = new Thread(td, "Thread-2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        System.out.println("Thread run execution started for " + Thread.currentThread().getName());
        System.out.println("Date formatter pattern is  " + dateFormat.get().toPattern());
        System.out.println("Formatted date is " + dateFormat.get().format(new Date()));
    } 

}
infoj
fonte
Ainda não vi o benefício de usar o ThreadLocal para gerar o ID exclusivo no exemplo um, se todo o seu objetivo for retornar um valor inteiro único incremental. `` public class ThreadId {// Número inteiro atômico que contém o próximo ID do segmento a ser atribuído private static final AtomicInteger nextId = new AtomicInteger (0); // Retorna o ID exclusivo do encadeamento atual, atribuindo-o, se necessário, public static int get () {return nextId..getAndIncrement (); }} `` `
dashenswen 25/04/19
7

Desde o lançamento do Java 8, há uma maneira mais declarativa de inicializar ThreadLocal:

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

Até o lançamento do Java 8, você precisava fazer o seguinte:

ThreadLocal<String> local = new ThreadLocal<String>(){
    @Override
    protected String initialValue() {
        return "init value";
    }
};

Além disso, se o método de instanciação (construtor, método de fábrica) da classe usada para ThreadLocalnão aceitar nenhum parâmetro, você poderá simplesmente usar referências de método (introduzidas no Java 8):

class NotThreadSafe {
    // no parameters
    public NotThreadSafe(){}
}

ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Nota: A avaliação é lenta, pois você está passando o java.util.function.Supplierlambda que é avaliado apenas quando ThreadLocal#geté chamado, mas o valor não foi avaliado anteriormente.

Andrii Abramov
fonte
5

Você precisa ter muito cuidado com o padrão ThreadLocal. Existem algumas desvantagens importantes, como Phil mencionou, mas uma que não foi mencionada é garantir que o código que configura o contexto ThreadLocal não seja "reentrante".

Coisas ruins podem acontecer quando o código que define as informações é executado uma segunda ou terceira vez, porque as informações no seu encadeamento podem começar a sofrer alterações quando você não esperava. Portanto, verifique se as informações do ThreadLocal não foram definidas antes de defini-las novamente.

Jeff Richley
fonte
2
A reentrada não é um problema se o código estiver preparado para lidar com isso. Na entrada, anote se a variável já está definida e, na saída, restaure seu valor anterior (se houver) ou remova-o (se não).
Supercat
1
@ Jeff, Cara, isso é verdade para todo código que você escreve, não apenas para o ThreadLocalpadrão. Se você fizer F(){ member=random(); F2(); write(member); }e F2 substituir o membro por um novo valor, obviamente write(member)não gravará mais o número que você random()editou. Isso é literalmente apenas senso comum. Da mesma forma, se o fizer F(){ F(); }, então dê sorte com seu loop infinito! Isso é verdade em todos os lugares e não é específico ThreadLocal.
Pacerier 19/09/17
@ Jeff, exemplo de código?
Pacerier
5

quando?

Quando um objeto não é seguro para threads, em vez da sincronização que dificulta a escalabilidade, atribua um objeto a cada thread e mantenha-o no escopo do thread, que é ThreadLocal. Um dos objetos usados ​​com mais freqüência, mas não com segurança de encadeamento, é o banco de dados Connection e JMSConnection.

Como ?

Um exemplo é o framework Spring que usa o ThreadLocal fortemente para gerenciar transações nos bastidores, mantendo esses objetos de conexão nas variáveis ​​ThreadLocal. Em um nível alto, quando uma transação é iniciada, ela obtém a conexão (e desativa a confirmação automática) e a mantém em ThreadLocal. em chamadas db adicionais, ele usa a mesma conexão para se comunicar com db. No final, ele pega a conexão do ThreadLocal e confirma (ou reverte) a transação e libera a conexão.

Acho que o log4j também usa o ThreadLocal para manter o MDC.

Calcário
fonte
5

ThreadLocal é útil quando você deseja ter algum estado que não deve ser compartilhado entre diferentes threads, mas deve ser acessível a partir de cada thread durante toda a sua vida útil.

Como exemplo, imagine um aplicativo Web, em que cada solicitação é atendida por um encadeamento diferente. Imagine que, para cada solicitação, você precise de um pedaço de dados várias vezes, o que é bastante caro para calcular. No entanto, esses dados podem ter sido alterados para cada solicitação recebida, o que significa que você não pode usar um cache simples. Uma solução simples e rápida para esse problema seria ter uma ThreadLocalvariável mantendo acesso a esses dados, para que você precise calculá-los apenas uma vez para cada solicitação. Obviamente, esse problema também pode ser resolvido sem o uso deThreadLocal , mas eu o projetei para fins ilustrativos.

Dito isto, tenha em mente que ThreadLocals são essencialmente uma forma de estado global. Como resultado, ele tem muitas outras implicações e deve ser usado somente após considerar todas as outras soluções possíveis.

Dimos
fonte
ThreadLocals NÃO são um estado global, a menos que você o torne um estado global. Eles são praticamente sempre recursos de pilha acessíveis. Você pode simular o encadeamento local simplesmente passando essa variável em todos os seus métodos (que é retardado); que não torná-lo estado global ...
Enerccio
É uma forma de estado global, no sentido em que é acessível de qualquer lugar do código (no mesmo contexto do thread). Isso vem com todas as repercussões, como não poder argumentar sobre quem lê e grava esse valor. Usar um parâmetro de função não é algo retardado, é uma boa prática promover interfaces limpas. No entanto, eu concordo com você que passar um parâmetro por toda a profundidade da sua base de código é um cheiro de código. Mas também acredito que, em muitas ocasiões, o uso do ThreadLocal é um cheiro de código em primeiro lugar que o levou até aqui, então é isso que se deve reconsiderar.
Dimos
Pode simplesmente fazer parte do estado do objeto, não precisa ser usado globalmente. Claro, você começa a sobrecarga pouco pouco, mas se vários objetos tem que ter estado diferente por thread, você pode usar ThreadLocalcomo campo de objeto ...
Enerccio
Você está certo. Eu provavelmente me deparei com vários usos indevidos de ThreadLocal, onde era acessível globalmente. Como você disse, ainda pode ser usado como um campo de classe que limita a visibilidade.
Dimos
4

Nada realmente novo aqui, mas descobri hoje que ThreadLocalé muito útil ao usar a Validação de Bean em um aplicativo da web. As mensagens de validação são localizadas, mas por padrão são usadas Locale.getDefault(). Você pode configurar o Validatorcom um diferente MessageInterpolator, mas não há como especificar o Localequando você chama validate. Portanto, você pode criar uma estática ThreadLocal<Locale>(ou, melhor ainda, um contêiner geral com outras coisas que talvez você precise ser ThreadLocale, em seguida, MessageInterpolatorescolher o Localeque é personalizado. O próximo passo é escrever um ServletFilterque use um valor de sessão ou request.getLocale()escolher o local e armazenar no seuThreadLocal referência.

Colselaw
fonte
4

Como foi mencionado por @unknown (google), seu uso é definir uma variável global na qual o valor referenciado pode ser único em cada thread. Seus usos normalmente envolvem o armazenamento de algum tipo de informação contextual vinculada ao encadeamento atual de execução.

Nós o usamos em um ambiente Java EE para passar a identidade do usuário para classes que não têm conhecimento do Java EE (não têm acesso ao HttpSession ou ao EJB SessionContext). Dessa forma, o código, que faz uso da identidade para operações baseadas em segurança, pode acessar a identidade de qualquer lugar, sem ter que passar explicitamente em todas as chamadas de método.

O ciclo de operações de solicitação / resposta na maioria das chamadas Java EE facilita esse tipo de uso, pois fornece pontos de entrada e saída bem definidos para definir e desabilitar o ThreadLocal.

Robin
fonte
4

O ThreadLocal garantirá que o acesso ao objeto mutável pelos vários threads no método não sincronizado seja sincronizado, significa tornar o objeto mutável imutável no método.

Isso é obtido dando uma nova instância de objeto mutável para cada thread que tenta acessá-lo. Portanto, é uma cópia local para cada segmento. Isso é um truque para tornar a variável de instância em um método a ser acessado como uma variável local. Como você sabe que a variável local do método está disponível apenas para o encadeamento, uma diferença é; As variáveis ​​locais do método não estarão disponíveis para o encadeamento quando a execução do método terminar, onde o objeto mutável compartilhado com o threadlocal estará disponível em vários métodos até que seja limpo.

Por definição:

A classe ThreadLocal em Java permite criar variáveis ​​que podem ser lidas e gravadas apenas pelo mesmo encadeamento. Portanto, mesmo que dois threads estejam executando o mesmo código e o código tenha uma referência a uma variável ThreadLocal, os dois threads não poderão ver as variáveis ​​ThreadLocal um do outro.

Cada um Threadem java contém ThreadLocalMapnele.
Onde

Key = One ThreadLocal object shared across threads.
value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

Atingindo o ThreadLocal:

Agora crie uma classe de wrapper para o ThreadLocal que manterá o objeto mutável como abaixo (com ou sem initialValue()).
Agora, o getter e o setter deste wrapper funcionarão na instância threadlocal em vez de no objeto mutável.

Se getter () de threadlocal não encontrou nenhum valor no threadlocalmap do Thread; em seguida, ele chamará o initialValue () para obter sua cópia particular em relação ao encadeamento.

class SimpleDateFormatInstancePerThread {

    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() {

        @Override
        protected SimpleDateFormat initialValue() {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") {
                UUID id = UUID.randomUUID();
                @Override
                public String toString() {
                    return id.toString();
                };
            };
            System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName());
            return dateFormat;
        }
    };

    /*
     * Every time there is a call for DateFormat, ThreadLocal will return calling
     * Thread's copy of SimpleDateFormat
     */
    public static DateFormat getDateFormatter() {
        return dateFormatHolder.get();
    }

    public static void cleanup() {
        dateFormatHolder.remove();
    }
}

Agora wrapper.getDateFormatter()chamará threadlocal.get()e isso verificará a instância currentThread.threadLocalMapcontém esta (threadlocal).
Se sim, retorne o valor (SimpleDateFormat) para a instância threadlocal correspondente, caso
contrário, adicione o mapa com essa instância threadlocal, initialValue ().

Com isso, a segurança do thread é alcançada nessa classe mutável; por cada thread está trabalhando com sua própria instância mutável, mas com a mesma instância ThreadLocal. Significa que todo o encadeamento compartilhará a mesma instância ThreadLocal da chave, mas diferentes instâncias SimpleDateFormat como valor.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java

Kanagavelu Sugumar
fonte
3

As variáveis ​​locais de encadeamento são frequentemente usadas para impedir o compartilhamento em projetos baseados em Singletons mutáveis ​​ou variáveis ​​globais.

Ele pode ser usado em cenários como fazer uma conexão JDBC separada para cada encadeamento quando você não estiver usando um Conjunto de Conexões.

private static ThreadLocal<Connection> connectionHolder
           = new ThreadLocal<Connection>() {
      public Connection initialValue() {
           return DriverManager.getConnection(DB_URL);
          }
     };

public static Connection getConnection() {
      return connectionHolder.get();
} 

Quando você chama getConnection, ele retornará uma conexão associada a esse thread. O mesmo pode ser feito com outras propriedades como dateformat, contexto de transação que você não deseja compartilhar entre threads.

Você também pode ter usado variáveis ​​locais para o mesmo, mas esses recursos geralmente demoram na criação, portanto, você não deseja criá-las repetidamente sempre que executar alguma lógica de negócios com elas. No entanto, os valores ThreadLocal são armazenados no próprio objeto de encadeamento e, assim que o encadeamento é coletado de lixo, esses valores desaparecem também.

Este link explica muito bem o uso do ThreadLocal.

bpjoshi
fonte
2
Neste exemplo, há uma questão importante: quem é o responsável pelo fechamento da conexão? Em vez de criar esta conexão como valor inicial, deixe o consumidor criar a conexão explicitamente e vinculá-la ao thread via ThreadLocal. O criador da conexão também é responsável pelo fechamento. Isso não está claro neste exemplo. Criar e vincular a conexão também pode ser oculto em uma estrutura simples por algo como Transaction.begin () e Transaction.end ().
Rick-Rainer Ludwig,
2

A classe ThreadLocal em Java permite criar variáveis ​​que podem ser lidas e gravadas apenas pelo mesmo encadeamento. Portanto, mesmo que dois threads estejam executando o mesmo código e o código tenha uma referência a uma variável ThreadLocal, os dois threads não poderão ver as variáveis ​​ThreadLocal um do outro.

Consulte Mais informação

Pritesh Patel
fonte
2

Existem três cenários para usar um auxiliar de classe como SimpleDateFormat no código multithread, o melhor é usar ThreadLocal

Cenários

1- Usando o objeto de compartilhamento com a ajuda do mecanismo de bloqueio ou sincronização , que torna o aplicativo lento

2- Usando como objeto local dentro de um método

Nesse cenário, se tivermos 4 encadeamentos, cada um chamará um método 1000 vezes, teremos
4000 objetos SimpleDateFormat criados e aguardando o GC apagá-los

3- Usando ThreadLocal

se tivermos 4 threads e demos a cada thread uma instância SimpleDateFormat
, teremos 4 threads , 4 objetos de SimpleDateFormat.

Não há necessidade de mecanismo de bloqueio e criação e destruição de objetos. (Boa complexidade de tempo e complexidade de espaço)

mohsen.nour
fonte
2

[Para referência] O ThreadLocal não pode resolver problemas de atualização do objeto compartilhado. É recomendável usar um objeto staticThreadLocal que seja compartilhado por todas as operações no mesmo encadeamento. O método [Obrigatório] remove () deve ser implementado pelas variáveis ​​ThreadLocal, especialmente ao usar conjuntos de encadeamentos nos quais os encadeamentos geralmente são reutilizados. Caso contrário, isso pode afetar a lógica comercial subsequente e causar problemas inesperados, como vazamento de memória.

Jovem
fonte
1

Armazenamento em cache, em algum momento você deve calcular o mesmo valor por muito tempo, armazenando o último conjunto de entradas em um método e o resultado pode acelerar o código. Ao usar o Thread Local Storage, você evita pensar em travar.

Ian Ringrose
fonte
1

ThreadLocal é uma funcionalidade especialmente provisionada pela JVM para fornecer um espaço de armazenamento isolado apenas para threads. como o valor da variável com escopo da instância, são vinculados a uma determinada instância apenas de uma classe. cada objeto tem seus únicos valores e eles não podem se ver. assim como o conceito de variáveis ​​ThreadLocal, elas são locais para o segmento, no sentido de instâncias de objeto, outro segmento, exceto aquele que o criou, não pode vê-lo. Veja aqui

import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;


public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(1000);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement());


// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
    return threadId.get();
}

public static void main(String[] args) {

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

    new Thread(() -> IntStream.range(1, 3).forEach(i -> {
        System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get());
    })).start();

}
}
Ajay Kumar
fonte
1

O Threadlocal fornece uma maneira muito fácil de obter a reutilização de objetos com custo zero.

Eu tive uma situação em que vários threads estavam criando uma imagem de cache mutável, em cada notificação de atualização.

Usei um Threadlocal em cada thread e, em seguida, cada thread precisaria redefinir a imagem antiga e atualizá-la novamente a partir do cache em cada notificação de atualização.

Objetos reutilizáveis ​​comuns de conjuntos de objetos têm um custo de segurança de encadeamento associado a eles, enquanto essa abordagem não possui nenhum.

Dev Amitabh
fonte
0

Experimente este pequeno exemplo, para ter uma idéia da variável ThreadLocal:

public class Book implements Runnable {
    private static final ThreadLocal<List<String>> WORDS = ThreadLocal.withInitial(ArrayList::new);

    private final String bookName; // It is also the thread's name
    private final List<String> words;


    public Book(String bookName, List<String> words) {
        this.bookName = bookName;
        this.words = Collections.unmodifiableList(words);
    }

    public void run() {
        WORDS.get().addAll(words);
        System.out.printf("Result %s: '%s'.%n", bookName, String.join(", ", WORDS.get()));
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Book("BookA", Arrays.asList("wordA1", "wordA2", "wordA3")));
        Thread t2 = new Thread(new Book("BookB", Arrays.asList("wordB1", "wordB2")));
        t1.start();
        t2.start();
    }
}


Saída do console, se o encadeamento BookA for feito primeiro:
Resultado BookA: 'wordA1, wordA2, wordA3'.
Livro de resultadosB: 'wordB1, wordB2'.

Saída do console, se o encadeamento BookB for feito primeiro:
Resultado BookB: 'wordB1, wordB2'.
Livro de resultadosA: 'wordA1, wordA2, wordA3'.

DigitShifter
fonte