Recentemente, li o código da camada de estrutura do Android e vi a classe Threadlocal. Eu estava um pouco desconhecido, então li vários blogs relacionados, um por um. Em seguida, estudei o código -fonte e descobri que meu entendimento era diferente das postagens do blog que li antes, então decidi escrever um artigo para falar sobre meu entendimento, esperando que ele possa desempenhar o seguinte papel:
- pode limpar os resultados da pesquisa e aprofundar sua compreensão;
- Isso pode desempenhar um papel na atração de jade e ajudar os alunos interessados a limpar suas idéias;
- Compartilhe sua experiência de aprendizado e comunique e aprenda com todos.
1. O que é Threadlocal
Threadlocal é a classe básica da biblioteca de classes Java, sob o pacote java.lang;
A explicação oficial é a seguinte:
Implementa um armazenamento local de thread, ou seja, uma variável para a qual cada encadeamento tem seu próprio valor. Todos os threads compartilham o mesmo objeto Threadlocal, mas cada um vê um valor diferente ao acessá -lo, e as alterações feitas por um thread não afetam os outros threads. A implementação suporta valores nulos.
O significado geral é:
O mecanismo de armazenamento local dos threads pode ser implementado. A variável Threadlocal é uma variável que pode ter valores diferentes em encadeamentos diferentes. Todos os threads podem compartilhar o mesmo objeto Threadlocal, mas threads diferentes podem obter valores diferentes quando acessados, e as alterações de qualquer encadeamento não afetarão outros threads. As implementações de classe suportam valores nulos (os valores nulos podem ser passados e acessados em métodos definidos e obtidos).
Em resumo, existem três características:
- Obtenha valores diferentes quando acessados por diferentes threads
- Qualquer alteração do fio não afetará outros threads
- Suporte nulo
Os seguintes são exemplos desses recursos. Primeiro, definimos uma classe de teste. Nesta classe, verificamos os três recursos mencionados acima. A definição de classe é a seguinte:
Test.java
public class Test {// Definir ThreadLocal Private Static StrathLocal Nome; public static void main (string [] args) lança exceção {name = new Threadlocal (); // Definir thread AthRead a = new Thread () {public void run () {System.out.println ("Antes da Invoke, o valor é:" A "); System.out.println (" Após o conjunto de invocados, o valor é: "+name.get ());}}; // Definir Thread bthread b = new Thread () {public void run () {system.out.println (" Antes do Invoke Set, é: "+name.get ()); name.set (" thread); : "+name.get ());}}; // não invocar Definir, imprimir o valor é nullsystem.out.println (name.get ()); // Invoke Set para preencher um nome de start.St (" Print thread "); // ship thread aa.start (); a.Join. Bb.start (); b.join (); // imprima o valor após alterar o valor por thread bsystem.out.println (name.get ())}}Análise de código:
A partir da definição, podemos ver que apenas um objeto Threadlocal é declarado e os outros três threads (encadeamento principal, encadeamento A e Thread B) compartilham o mesmo objeto; Em seguida, o valor do objeto é modificado em diferentes encadeamentos e o valor do objeto é acessado em diferentes encadeamentos, e o resultado é emitido a ser exibido no console.
Veja os resultados:
A partir dos resultados da saída do console, você pode ver que existem três saídas nulas. Isso ocorre porque o objeto não foi atribuído antes da saída, que verificou o recurso de suporte a nulo. Além disso, pode-se descobrir que, em cada encadeamento, modifiquei o valor do objeto, mas quando outros threads acessam o objeto, ele não é o valor modificado, mas o valor do local do encadeamento; Isso também verifica os outros dois recursos.
2. O papel do Threadlocal
Todo mundo sabe que seus cenários de uso são principalmente programação multithread. Quanto à sua função específica, como você diz isso? Eu acho que isso só pode ser definido de maneira geral, porque os atributos funcionais de uma coisa limitarão o pensamento de todos depois de ser definido. Por exemplo, facas de cozinha são usadas para cortar vegetais, e muitas pessoas não o usam para cortar melancias.
Aqui, falarei sobre minha compreensão de sua função apenas para referência e espero que seja útil. Vamos descrever dessa maneira. Quando um programa com várias tacadas precisa encapsular algumas tarefas da maioria dos threads (ou seja, algum código no método de execução), o Threadlocal pode ser usado para envolver variáveis de membros relacionadas a roscas no órgão de encapsulamento para garantir a exclusividade do acesso a roscas, e todos os threads podem compartilhar um objeto de encapsulamento; Você pode se referir ao Looper no Android. Os programadores que não conseguem descrever problemas com o código não são bons programadores;
Veja o código: uma ferramenta para indicar o código demorado de um thread (criado para ilustrar o problema)
StatisticCosttime.java
// classe que estatística, o custo do tempo da classe StatisticCostTime {// grava o starttime // private threadlocal startTime = new Threadlocal (); private longo tempo de início; // shreadlocal de shreadlocal = new threadlocal (); stattime privado; InstanceFactory.Instance;} classe estática privada Instância Factory {estático privado StatisticCostCostTime Instância = new StatisticCostTime ();} // startpublic void start () {// starttime.set (System. Nanotime ();); startTime.get ()); CostTime = System.nanotime () - startTime;} public long getStartTime () {return startTime; // return starttime.get.get ();} public long getCostTime () {// return CostTime.get.get (); retorno custo;}Ok, o design da ferramenta está concluído, agora o usamos para contar os tópicos demorados e tentar:
Main.java
public class Main {public static void main (string [] args) lança exceção {// Defina o thread Athread a = new Thread () {public void run () {try {// Inicie o registro timestatisticCostTime.ShareInstance (). Start (); Sleep (200); // Imprima o tempo de início de A System.out.println ("A-startTime:"+statisticCostTime.ShareInstance (). GetStartTime ()); // encerrar o registrostatisticCosttime.ShareInstance (). End (); // imprima a hora do custo de um system.out.println. e) {}}}; // start aa.start (); // define thread bthread b = new thread () {public void run () {try {// grava o tempo de início do b1statisticCostTime.ShareInstance (). Start (); Sleep (100); consolesystem.out.println ("b1-startTime:"+statisticCosttime.ShareInstance (). getStartTime ()); // Recorde final Horário de início do b1statisticCostTime.ShareInstance (). end (); // Imprima o tempo de custo de B1system.out.println ("b1:"+statisticCostTime.ShareInstance (). GetCostTime ()); // Inicie o tempo de registro do b2statisticCostTime.ShareInstance (). Start (); Sleep (100); // Print Hora de Iniciar da do início do B2system.out.println ("b2-startTime:"+statisticCostTime.ShareInstance (). GetStartTime ()); // Tempo de gravação final do b2statisticCostTime.ShareInstance (). End (); // Tempo de custo de impressão do tempo B2system.out.println ("b2:"+statisticCostTime.ShareInstance (). GetCostTime ());} catch (Exceção e) {}}}; b.start ();}}Após a execução do código, o resultado da saída é o seguinte: A precisão do resultado da saída é nanossegundos.
Depende se o resultado é diferente do que esperávamos. Descobri que o resultado de A deveria ser aproximadamente igual a B1+B2. Como é que se torna o mesmo que B2? A resposta é que, quando definimos as variáveis de início e tempo de custo, a intenção original não deve ser compartilhada, mas deve ser exclusiva do thread. Aqui, as variáveis são compartilhadas com o singleton; portanto, ao calcular o valor de A, o tempo de início foi realmente modificado por B2, portanto, o mesmo resultado que B2 é a saída.
Agora, vamos abrir a parte comentada no StatisticCosttime e tentá -lo alterando -o para o método de declaração do Threadlocal.
Veja os resultados:
Ah! Isso alcançou o efeito esperado. Neste momento, alguns estudantes diriam que isso não é acesso concorrente de threads e posso garantir a segurança do thread desde que use threadlocal? A resposta é não! Primeiro de tudo, podemos descobrir por que existem problemas de segurança de tópicos, mas existem apenas duas situações:
1. Você compartilhou recursos que não devem ser compartilhados entre os threads;
2. Você não garante acesso ordenado a recursos compartilhados entre threads;
O primeiro pode ser resolvido pelo método de "tempo de troca espacial", usando o Threadlocal (você também pode declarar diretamente variáveis locais de encadeamento), e o último pode ser resolvido pelo método "tempo de troca espacial", que obviamente não é o que o Threadlocal pode fazer.
3. Princípio da Threadlocal
O princípio da implementação é realmente muito simples. Toda vez que a operação de leitura e gravação para o objeto Threadlocal é na verdade uma operação de leitura e gravação para o objeto Valores do Thread; Aqui esclarecemos que não há criação de uma cópia da variável, porque o espaço de memória alocado pela variável não é usado para armazenar o objeto T, mas os valores do encadeamento são usados para armazenar o objeto T; Toda vez que chamamos o método do conjunto ThreadLocal no thread, é na verdade um processo de gravação do objeto para o objeto Valores correspondentes do encadeamento; Ao chamar o método ThreadLocal GET, na verdade é um processo de obter o objeto do objeto Valores correspondentes do Thread.
Veja o código -fonte:
Conjunto de variáveis de membros threadlocal
/*** Define o valor dessa variável para o thread atual. Se definido como * {@code null}, o valor será definido como nulo e a entrada subjacente * ainda estará presente. * * @param valor o novo valor da variável para o encadeamento do chamador. */public void Set (valor t) {thread currentThread = thread.currentThread (); Valores valores = valores (currentThread); if (valores == null) {valores = InitializeValues (currentThread); } valores.put (this, value);}Método de membro do Treadlocal Obtenha
/*** Retorna o valor dessa variável para o thread atual. Se uma entrada * ainda não existir para essa variável neste thread, esse método * criará uma entrada, preenchendo o valor com o resultado de * {@link #InitialValue ()}. * * @RETURN O valor atual da variável para o thread de chamada. */@Supressorwarnings ("desmarcado") public t get () {// otimizado para o caminho rápido. Thread currentThread = thread.currentThread (); Valores valores = valores (currentThread); if (valores! = null) {object [] tabela = valores.table; int index = hash & valores.mask; if (this.reference == tabela [index]) {return (t) tabela [índice + 1]; }} else {valores = InitializeValues (currentThread); } retornar (t) valores.getAftermiss (this);}Método de Membro Threadlocal InitializeValues
/*** Cria a instância dos valores para este thread e tipo de variável. */Valores InitializeValues (Thread Current) {return Current.localValues = new Values ();}Valores do método do membro threadlocal
/*** Obtém a instância dos valores para este thread e tipo de variável. */Valores valores (Thread Current) {return Current.localValues;}Então, como você lê e escreve objetos nesses valores?
Os valores existem como uma classe interna de Threadlocal; Esses valores incluem um objeto de matriz importante [], que é a parte principal de responder à pergunta. É usado para armazenar vários tipos de variáveis de palha no thread. Portanto, a questão é: como você garante que você não obtém valores de outros tipos ao tomar variáveis de um determinado tipo? Em geral, um mapa será mapeado de acordo com o valor-chave; Sim, a ideia é essa ideia, mas não é implementada com o mapa aqui, é um mecanismo de mapa implementado com um objeto []; No entanto, se você deseja usar o mapa para entendê -lo, não é possível porque o mecanismo é o mesmo; A chave realmente corresponde à fraca referência do Threadlocal, e o valor corresponde ao objeto que passamos.
Vamos explicar como usar o objeto [] para implementar o mecanismo de mapa (consulte a Figura 1); Ele usa a paridade do subscrito da matriz para distinguir a chave e o valor, ou seja, a tabela abaixo armazena a chave na posição par e o número ímpar armazena o valor. É assim que é feito. Se os alunos interessados quiserem conhecer a implementação do algoritmo, eles podem estudá -lo em profundidade, não o explicarei em detalhes aqui.
Com base no primeiro exemplo anterior, a situação de armazenamento é analisada:
Quando o programa é executado, existem três threads A, B e Main. Quando o nome.Set () é chamado no thread, três espaços de memória idênticos são alocados na área da pilha para três instâncias de threads ao mesmo tempo para armazenar o objeto Valores, usando a referência de nome como a chave e o objeto específico é armazenado como o valor em três objetos diferentes [] (consulte a figura abaixo):
4. Resumo
Threadlocal não pode resolver completamente o problema de simultaneidade durante a programação de vários thread. Esse problema também requer soluções diferentes para escolher, de acordo com diferentes situações, "espaço para o tempo" ou "tempo para o espaço".
A maior função do ThreadLocal é converter variáveis compartilhadas em roscas em variáveis locais de encadeamento para obter isolamento entre os encadeamentos.
O exposto acima é sobre entender rapidamente Threadlocal em Java. Espero que seja útil para todos. Se houver alguma falha, deixe uma mensagem para apontá -la. Obrigado amigos pelo seu apoio a este site.