Prefácio
O problema de segurança do thread do multithreading é sutil e inesperado, porque a ordem das operações em multithreads é imprevisível sem a sincronização adequada. Vários tópicos que acessam a mesma variável compartilhada são particularmente propensos a problemas de simultaneidade, especialmente quando vários tópicos precisam escrever uma variável compartilhada, a fim de garantir a segurança do thread,
Geralmente, os usuários precisam executar a sincronização apropriada ao acessar variáveis compartilhadas, conforme mostrado na figura abaixo:
Pode -se observar que a medida de sincronização geralmente está travando, o que exige que o usuário tenha um certo entendimento da trava, o que obviamente aumenta a carga do usuário. Então, existe uma maneira de criar uma variável, quando cada thread a acessa, ele acessa as variáveis de seu próprio thread? De fato, o Throalocal pode fazer isso. Observe que o surgimento do Threadlocal não parece resolver os problemas acima.
Threadlocal é fornecido no pacote JDK. Ele fornece variáveis locais de encadeamento. Ou seja, se você criar uma variável Threadlocal, cada thread que acessa essa variável terá uma cópia local da variável. Quando vários threads operam essa variável, eles realmente operam as variáveis em sua própria memória local, evitando os problemas de segurança do encadeamento. Depois de criar uma variável threadlocal,
Cada encadeamento copiará uma variável para sua memória local, conforme mostrado na figura abaixo:
Ok, agora vamos pensar em uma pergunta: o princípio de implementação do Threadlocal e como o Threadlocal é implementado como um método de isolamento de threads variável, internamente?
Primeiro, precisamos olhar para a estrutura do diagrama de classe do Threadlocal, como mostrado na figura a seguir:
como
Como pode ser visto no diagrama de classe acima, há um threadlocals e hereitableThreadLocals na classe Thread. Ambos os tipos de variáveis são Threadlocalmap e Threadlocalmap é um hashmap personalizado. Por padrão, ambas as variáveis em cada encadeamento são nulas. Eles só serão criados quando o thread chamar o conjunto de threadlocal ou obter método pela primeira vez.
De fato, as variáveis locais de cada encadeamento não são armazenadas na instância do threadlocal, mas são armazenadas na variável Threadlocals do encadeamento de chamada. Em outras palavras, as variáveis locais do tipo Threadlocal são armazenadas no espaço de memória de encadeamento específico.
Threadlocal é na verdade um shell. Ele coloca o valor do valor no encadeamento de encadeamento de chamadas Locals através do método de conjunto e o armazena. Quando o thread de chamada chama seu método GET, ele é retirado da variável ThreadLocals do encadeamento atual. Se o encadeamento de chamada não terminar, a variável local será armazenada na variável ThreadLocals do encadeamento de chamada.
Portanto, quando você não precisa usar variáveis locais, pode excluir a variável local da variável ThreadLocals do encadeamento atual chamando o método de remoção da variável Threadlocal. Algumas pessoas podem perguntar por que o ThreadLocals foi projetado como uma estrutura de mapa? É óbvio que cada encadeamento pode ser associado a várias variáveis de threadlocal.
Em seguida, podemos inserir o código -fonte no Threadlocal, como mostrado no código a seguir:
Ele analisa principalmente a lógica de implementação dos três métodos definidos, obtenha e remove, como segue:
Vejamos o método Set (T Var1) primeiro
public void Set (t var1) {// (1) Obtenha o encadeamento atual var2 = Thread.currentThread (); // (2) O encadeamento atual é usado como a chave para encontrar a variável de encadeamento correspondente. Se encontrado, defina threadlocal.threadlocalmap var3 = this.getMap (var2); if (var3! = null) {var3.set (this, var1); } else {// (3) A primeira chamada é criada para criar o hashmap correspondente para o encadeamento atual this.createmap (var2, var1); }}Como mencionado acima do código (1), primeiro obtenha o thread de chamada e use o thread atual como um parâmetro para chamar o método getMap (var2). O código getMap (Thread Var2) é o seguinte:
Threadlocal.threadlocalmap getMap (thread var1) {return var1.threadlocals; }Pode -se observar que o que o getMap (var2) faz é obter os próprios locais de threads variáveis do Thread e a variável Threadlocal está ligada à variável de membro do encadeamento.
Se o getMap (var2) retornar não vazio, defina o valor do valor como ThreadLocals, ou seja, coloque o valor da variável atual nos locais de threads variáveis de memória do encadeamento atual. ThreadLocals é uma estrutura de hashmap, onde a chave é a referência do objeto de instância ThreadLocal atual, e o valor é o valor passado pelo método definido.
Se o getMap (var2) retornar vazio, isso significa que o método de conjunto é chamado na primeira vez e, em seguida, a variável ThreadLocals do encadeamento atual será criada. Vamos ver o que é feito no createMap (var2, var1)?
void CreateMap (Thread var1, t var2) {var1.ThreadLocals = new ThreadLocal.ThreadLocalMap (this, var2); }O que você pode ver é criar a variável ThreadLocals do encadeamento atual.
Em seguida, vejamos o método get (), o código é o seguinte:
public t get () {// (4) obtenha o encadeamento atual var1 = thread.currentThread (); // (5) Obtenha o ThreadLocals Variable ThreadLocal.ThreadLocalMap var2 = this.getMap (var1); // (6) Se o ThreadLocals não for nulo, o valor da variável local correspondente será retornado se (var2! = Null) {threadlocal.threadlocalmap.entry var3 = var2.getEntry (this); if (var3! = null) {objeto var4 = var3.Value; retornar var4; }} // (7) Se o ThreadLocals estiver vazio, a variável do membro ThreadLocals do encadeamento atual é inicializada. retornar this.setInitialValue (); }Código (4) Primeiro obtenha a instância do thread atual. Se a variável ThreadLocals do encadeamento atual não for nulo, ele retornará diretamente a variável local do encadeamento atual. Caso contrário, execute o código (7) para inicializar, e o código do setInitialValue () é o seguinte:
private t setinitialValue () {// (8) inicializado para objeto nulo var1 = this.initialValue (); Thread var2 = Thread.currentThread (); Threadlocal.threadlocalmap var3 = this.getMap (var2); // (9) se a variável ThreadLocals da variável do encadeamento atual não estiver vazia se (var3! = Null) {var3.set (this, var1); // (10) Se a variável ThreadLocals do encadeamento atual estiver vazia} else {this.createmap (var2, var1); } retornar var1; }Como mencionado acima, se a variável ThreadLocals do encadeamento atual não estiver vazia, o valor variável local do encadeamento atual será definido como NULL. Caso contrário, o CreateMap é chamado para a variável CreateMap do thread atual.
Em seguida, analisamos o método de Remons () do Void (), o código é o seguinte:
public void remover () {threadlocal.threadlocalmap var1 = this.getMap (thread.currentThread ()); if (var1! = null) {var1.remove (this); }}Como mencionado acima, se a variável ThreadLocals do encadeamento atual não estiver vazia, a variável local especificada na instância ThreadLocal no encadeamento atual será excluída.
Em seguida, vamos dar uma olhada na demonstração específica, o código é o seguinte:
/*** Criado por Cong em 2018/6/3. */public class ThreadLocalTest {// (1) Print Função estática Void Print (String str) {//1.1 Imprima o valor da variável localVariable na memória local do sistema Thread.out.println (str + ":" + localVariable.get ()); //1.2 Limpe a variável localVariable na memória local do thread atual //localvariable.remove (); } // (2) Crie threadlocal variável estática threadlocal <string> localVariable = new Threadlocal <> (); public static void main (string [] args) {// (3) Crie thread um thread threadOne = new Thread (new Runnable () {public void run () {//3.1 Defina o valor da variável local LocalVariable na fios One LocalVariable.Set ("Variável local de Thread 1"); //3.2 chamada a função imprimida (" System.out.println ("Resultado Após a remoção da variável local de Thread 1" + ":" + LocalVariable.get ()); //(4) Crie Thread Two Thread Threadtwo = new Thread (new Runnable () {public void run () {//4.1 Defina o valor da variável local LocalVariable em Thread One LocalVariable.Set ("Variável local de Thread 2"); //4. 2 " +": " + localVariable.get ());}}); // (5) Inicie o Thread ThreadOne.start (); threadtwo.start (); }}O código (2) cria uma variável Threadlocal;
Códigos (3) e (4) criam threads 1 e 2, respectivamente;
Código (5) inicia dois threads;
Código 3.1 No encadeamento 1 Defina o valor do localVariable através do método de conjunto. Essa configuração é na verdade uma cópia na memória local do Thread 1. Esta cópia não pode ser acessada pelo thread 2. O código 3.2 chama a função de impressão e o código 1.1 obtém o valor do localVariable na memória local do encadeamento atual (encadeamento 1) através da função GET;
O thread 2 é executado semelhante ao encadeamento 1.
Os resultados da operação são os seguintes:
Aqui precisamos prestar atenção ao problema de vazamento de memória do Threadlocal
Cada encadeamento possui uma variável de membro chamada ThreadLocals dentro. O tipo de variável é hashmap. A chave é essa referência à variável Threadlocal que definimos e o valor é o valor quando definimos. A variável local de cada encadeamento é armazenada nos próprios locais de threads variáveis de memória do thread. Se o encadeamento atual não desaparecer, essas variáveis locais serão armazenadas até.
Portanto, pode causar vazamento de memória; portanto, depois de usá -lo, lembre -se de chamar o método Remover do ThreadLocal para excluir as variáveis locais nos lotes de threads do encadeamento correspondente.
Depois de descompactar os comentários no código 1.2, execute novamente, e o resultado da execução é o seguinte:
Já pensamos em uma pergunta como esta: obtemos o valor da variável Threadlocal definida no thread pai no thread filho?
Aqui podemos dizer que o valor da variável Threadlocal definido no encadeamento pai não pode ser obtido no encadeamento filho. Então, existe uma maneira de fazer com que o encadeamento filho acesse o valor no thread pai? O HereitableThreadLocal surgiu no futuro. HeritableThreadLocal herda do ThreadLocal e fornece um recurso que as crianças podem acessar variáveis locais definidas no thread pai.
Primeiro, vamos para o código -fonte da classe IndeitableThreadLocal para ler, como segue:
classe pública hereitableThreadLocal <T> estende Threadlocal <T> {public HeritableThReadLocal () {} // (1) Protected T ChildValue (t var1) {return var1; } // (2) ThreadLocalMap getMap (Thread var1) {return var1.IritableThreadLocals; } // (3) void createMap (thread var1, t var2) {var1.IritableThreadLocals = new ThreadLocalMap (this, var2); }}Você pode ver que o HeritableThreadLocal herda Threadlocal e Reescrito três métodos. O código acima foi marcado. Código (3) Pode -se observar que o HeritableThReadLocal reescreve o método CreateMap, para que seja visto que, quando o método de conjunto é chamado pela primeira vez, a instância da variável heitableThreadLocals do encadeamento atual é criada, em vez de threadlocals.
Código (2) Você pode saber que, quando o método GET é chamado para obter a variável de mapa interna do encadeamento atual, o HeritableThreadLocals é obtido, não o ThreadLocals.
O ponto -chave está aqui, quando o código reescrito (1) é executado e como implementar que o encadeamento Child pode acessar as variáveis locais do thread pai. Começando com o código criado por thread, o construtor padrão do thread e o construtor da classe Thread.java são os seguintes:
/*** Criado por Cong em 2018/6/3. */ public Thread (Runnable Target) {init (null, Target, "Thread-" + NextThreadnum (), 0); } private void init (ThreadGroup G, Target Runnable, Nome da String, StackSize Long, AccessControlContext acc) {// ... // (4) Obtenha o thread atual pai pai = currentThread (); // ... // (5) Se a variável HeritableThReadLocals do fio pai não for nulo se (parent.IritableThreadLocals! = Null) // (6) define a variável de linha de linha de linha. this.stacksize = Stacksize; tid = nextThreadId (); }Ao criar um thread, o método init será chamado no construtor. Como mencionado anteriormente, a classe HeritableThreadLocal GET e o método Set opera a variável HeritableThReadLocals, de modo que a variável hereitableThreadLocal aqui não é nula, portanto, o código (6) será executado. Vamos ver o código -fonte do método CreateIritedMap, como segue:
estático threadlocalmap createIritedmap (threadlocalmap parentmap) {return threadLocalMap (parentmap); }Você pode ver que o CreateIritedMap usa a variável HeritableThreadLocals do thread pai como o construtor para criar uma nova variável ThreadLocalmap e, em seguida, atribui -a à variável HeritableThreadLocals do encadeamento Child. Em seguida, digite o construtor do threadlocalmap. O código -fonte é o seguinte:
Private ThreadLocalMap (ThreadLocalMap parentMap) {Entry [] parentTable = parentmap.table; int len = parentTable.length; defesa (LEN); tabela = nova entrada [len]; for (int j = 0; j <len; j ++) {entrada e = parentTable [j]; if (e! = null) {@suppresswarnings ("desmarcado") threadlocal <ject> key = (threadlocal <ject>) e.get (); if (key! = null) {// (7) chame o valor do objeto de método substituído = key.childValue (e.value); // retorna e.Value entrada c = nova entrada (chave, valor); int h = key.threadlocalhashcode & (len - 1); while (tabela [h]! = null) h = nextIndex (h, len); tabela [h] = c; tamanho ++; }}}}}}O que o código acima faz é copiar o valor da variável de membro da linha do thread do painel para o novo objeto Threadlocalmap, e o código (1) reescrito pelo código (7) HeRitableThreadLocal Class também é exibido.
Em geral: a classe HeritableThreadlocal reescreve o código (2) e (3) e salva as variáveis locais na variável herança de tratamento dereadlocais do encadeamento específico. Quando o thread define a variável através do método Set ou Get da instância do InheRitableThreadLocals, ele criará a variável HeritableThreadLocals do thread atual. Quando o tópico pai cria o tópico infantil,
O construtor copiará a variável local na variável HIRITableThreadLocals no encadeamento pai e copiará na variável herançaBleThreadLocals do encadeamento filho.
Depois que o princípio é bem compreendido, vamos dar um exemplo para verificar o que sabemos acima, como segue:
pacote com.hjc;/*** Criado por Cong em 2018/6/3. */public class IritableThreadLocalTest {// (1) Crie variável de thread public static shreadlocal <string> threadLocal = new Threadlocal <string> (); public static void main (string [] args) {// (2) Defina o Thread Variable Threadlocal.set ("Hello java"); // (3) Iniciar o thread do thread Child Child = new Thread (new Runnable () {public void run () {// (4) O valor do encadeamento infantil produz o encadeamento variável system.out.println ("subthread:" + threadlocal.get ());}}); thread.start (); // (5) O encadeamento principal gera o encadeamento Value Value System.out.println ("Thread Parent:" + Threadlocal.get ()); }}Os resultados da operação são os seguintes:
Ou seja, depois que a mesma variável Threadlocal é definida no segmento pai, ela não pode ser obtida no encadeamento filho. De acordo com a introdução na seção anterior, esse deve ser um fenômeno normal, porque o encadeamento atual é um encadeamento filho quando o encadeamento filho chama o método GET, e o método de conjunto é chamado para definir a variável do encadeamento é o encadeamento principal. Os dois são threads diferentes e, naturalmente, o encadeamento infantil retorna nulo ao acessar.
Então, existe uma maneira de fazer com que o encadeamento filho acesse o valor no thread pai? A resposta é sim, então use o herança de tratamento analisado pelo nosso princípio acima.
Modifique o código (1) do exemplo acima para:
// (1) Crie um thread variável public static threadlocal <string> threadLocal = new InheritableThreadLocal <String> ();
Os resultados da operação são os seguintes:
Pode -se observar que o valor variável do encadeamento agora pode ser obtido normalmente do thread filho. Então, em que circunstâncias as crianças precisam obter variáveis de threadlocal do thread pai?
Existem muitas situações, como a variável Threadlocal que armazena informações de login do usuário. É muito provável que os sublelhados também precisem usar as informações de login do usuário. Por exemplo, algum middleware precisa usar um ID de rastreamento unificado para gravar todo o link de chamada.
Uso de Threadlocal na Spring Solicle Scope Scope Bean
Sabemos que, ao configurar um feijão em XML na primavera, você pode especificar o atributo de escopo para configurar o escopo do feijão para ser singleton, protótipo, solicitação, sessão etc. O princípio de implementação do escopo do feijão é implementado usando Threadlocal. Se você deseja um feijão em seu recipiente de primavera para ter algum escopo da web,
Além dos atributos de escopo correspondentes necessários para configurar o nível do feijão, ele também deve ser configurado no web.xml da seguinte maneira:
<Ilanter> <Ilvier-class> org.springframework.web.context.request.requestContextListener </lirerv-class> </lister>
Aqui, analisamos principalmente dois métodos de requestContextListener:
public void requestinitialized (servletRequestEvent requestEvent)
e
Public void requestDestroyed (servletrequestevent requestEvent)
Quando uma solicitação da Web chegar, o método RequestInitialized será executado:
public void requestInitialized (servletRequestEvent requestEvent) {..... omita httpServletRequest request = (httpServletRequest) requestEvent.getServletRequest (); ServletRequestattributes atributos = new ServLetRequestAttributes (request); request.setAttribute (request_attributes_attribute, atributos); LocalEContextholder.setLocale (request.getLocale ()); // Defina o atributo como threadlocal variável requestContextholder.setRequestAttributes (atributos); } public static void setRequestAttributes (atributos requestAttributes) {setRequestAttributes (atributos, false); } public static void setRequestAttributes (atributos requestAttributes, herança booleana) {if (attributes == null) {resetRequestAttributes (); } else {// padrão herdável = false if (herdável) {herdableReQuestAttributesHolder.set (atributos); requestAttributesHolder.remove (); } else {requestAttributeSholder.set (atributos); herdableRereQuestAttributeSholder.remove (); }}}Você pode ver o código -fonte acima. Como o herdável padrão é falso, nossos valores de atributo são colocados no requestattributeShoder, e sua definição é:
Principal de Threadlocal de Threadlocal estático privado <RequestAtTrributes> requestattributesHolder = new NometThreadlocal <PolicStTributes> ("atributos de solicitação"); Principal de Threadlocal Static Private <RequestAtTtributes> herditableReQuestAttributeSholder = new NomeNheritableThreadLocal <RequestAttributes> ("Contexto da solicitação");Entre eles, chamadothreadlocal <T> estende Threadlocal <T>, por isso não é herdável.
Entre eles, chamadothreadlocal <T> estende Threadlocal <T>, por isso não é herdável.
NameIritableThreadLocal <T> estende a herança de linha <T>, por isso possui herança; portanto, o valor do atributo colocado no requestContextholder por padrão não pode ser obtido no encadeamento infantil.
Quando a solicitação termina, o método RequestDestroyed é chamado e o código -fonte é o seguinte:
public void requestDestroyed (servletRequestEvent requestEvent) {servletRequestAttributes atribues = (servletRequestAttributes) requestEvent.getServletRequest (). getAttribute (request_attributes_attribute); ServletRequestattributes ThreadAttributes = (ServLeTReQuestAttributes) requestContextholder.getRequestAttributes (); if (threadAttributes! = null) {// É muito provável que limpe o encadeamento do thread atual no thread de solicitação inicial se (atributes == null) {attritutes = threadtributes; } // Quando a solicitação terminar, limpe a variável Thread do encadeamento atual. LocalEContextholder.ResetLocalEcontext (); RequestContextholder.ReseTReQuestAttributes (); } if (atributes! = null) {attritutes.requestCompleted (); }}Em seguida, vamos dar uma olhada na lógica da chamada de solicitação da web no gráfico de tempo:
Ou seja, toda vez que uma solicitação da Web é iniciada antes de processar o contexto (aplicativo específico) no Tomcat, a propriedade RequestContextholder será definida após a correspondência do host, para que o requestattributesholder não esteja vazio e será liberado no final da solicitação.
Portanto, por padrão, os threads infantis do atributo colocados no RequestContextholder não podem ser acessados, e os grãos no escopo da solicitação da mola são implementados usando o ThreadLocal.
Em seguida, um exemplo de solicitação de simulação é executado, o código é o seguinte:
A configuração web.xml é a seguinte:
Por ser um escopo de solicitação, deve ser um projeto da web e o requestContextListener precisa ser configurado para web.xml.
<Ilanter> <Ilvier-class> org.springframework.web.context.request.requestContextListener </lirerv-class> </lister>
Em seguida, injete um feijão de escopo de solicitação no contêiner do COI. O código é o seguinte:
<bean id = "requestBean" scope = "request"> <propriedade name = "name" value = "hjc" /> <AOP: Scoped-proxy /> </shean>
O código de teste é o seguinte:
@Webresource ("/testService") classe pública testrpc {@aUTowired Private RequestBean requestInfo; @ResourCemapping ("Test") Teste de ação pública (contexto do ErrorContext) {ActionResult resultado = new ActionResult (); pvginfo.setName ("HJC"); Nome da sequência = requestInfo.getName (); resultado.setValue (nome); resultado de retorno; }}Como mencionado acima, primeiro configure o requestContextListener no web.xml e depois injete a instância do requestBean no contêiner do COI com o escopo da solicitação. Finalmente, a instância do RequestBean é injetada no TestRPC. O teste do método chama primeiro o SetentInfo Method SetName para definir o atributo Nome e, em seguida, obtenha o atributo Nome e retorne.
Aqui, se o objeto RequestInfo for Singleton, depois que vários threads chamam o método de teste ao mesmo tempo, cada thread é uma operação de conjunto. Esta operação não é atômica e causará problemas de segurança de threads. O escopo declarado aqui é o nível de solicitação e cada thread possui uma variável local com requestInfo.
O gráfico de tempo da solicitação de método de exemplo acima é a seguinte:
Precisamos nos concentrar no que acontece ao chamar o teste:
De fato, o requestInfo criado anteriormente é depois de ser procurado pelo CGLIB (se você estiver interessado, poderá estudar ScopeDProxyFactoryBean e outros tipos); portanto, quando você ligar para o SetName ou GetName, você será interceptado pelo DynamicadVisedInterceptor. O interceptador acabará chamando o método Get of Requestscope para obter as variáveis locais mantidas pelo thread atual.
A chave está aqui. Precisamos olhar para o código -fonte do método RequestScope Get da seguinte forma:
objeto público get (nome da string, objectFactory ObjectFactory) {requestAttributes attritutes = requestContextholder.currentRequestattributes (); // (1) objeto scopedObject = attributes.getAttribute (name, getScope ()); if (scopedObject == null) {scopeDObject = objectFactory.getObject (); // (2) attritutes.setAttribute (nome, scopedObject, getScope ()); // (3)} retornar ScopeDObject; }Pode -se observar que, quando uma solicitação for iniciada, o requestAttributesHolder será definido ligando para o requestContextListener.Requestinitialized in reEndContextListener.setRequestAttributess.
Depois que a solicitação é roteada para o método de teste do TESTRPC, a primeira vez que o método setName é chamado no método de teste, o método requestscope.get () será chamado. O código no método get (1) obtém o valor do conjunto de atributos salvo pela variável local da variável RequestAttributesHolder definida através do requestContextListener.Requestinitialized.
Em seguida, verifique se existe um atributo chamado requestInfo no conjunto de atributos. Como é a primeira chamada, não existe, o código será executado (2) e deixe o Spring criar um objeto RequestInfo e, em seguida, definir -o para o atributo Set atributos, ou seja, será salvo na memória local do thread de solicitação atual. Em seguida, retorne o objeto criado e chame o SetName do objeto criado.
Finalmente, o método getName é chamado no método de teste, e o método requestscope.get () será chamado. O código no método get (1) obtém o thread Local Variable RequestAttributes definido através do requestContextListener.Requestinitialized e, em seguida, veja se existe um atributo chamado requestInfo no conjunto de atributos.
Como o feijão denominado requestInfo foi definido como a variável Threadlocal quando é chamado para o SetName pela primeira vez, e o mesmo thread é chamado para o SetName e GetName, o objeto RequestInfo criado quando chama o SetName é retornado diretamente aqui e então seu método getName é chamado.
Até agora, entendemos o princípio da implementação do ThreadLocal e apontamos que o Threadlocal não suporta herança; Em seguida, explicamos imediatamente como o HeritableThreadLocal compensa o recurso que o ThreadLocal não suporta herança; Finalmente, introduzimos brevemente como usar o ThreadLocal na estrutura da primavera para implementar o bean do escopo do Reqeust.
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.