1.O que é ThreadLocal
ThreadLocal, localização de thread, pode privatizar o valor variável do thread armazenado. Pode-se entender simplesmente que ThreadLoacl pode fornecer a cada thread seu próprio espaço de armazenamento, que pode ser acessado a qualquer momento durante todo o processo de sobrevivência do thread, o que facilita muito a implementação de alguma lógica.
Os usos comuns incluem:
- Armazena informações de contexto de thread individuais. Por exemplo, armazenamento de ID, etc.;
- Torne as variáveis thread-safe. Como as variáveis se tornam variáveis locais dentro de cada thread, naturalmente não haverá problemas de simultaneidade;
- Reduza a passagem de parâmetros. Por exemplo, crie uma ferramenta de rastreamento que possa gerar todas as informações de todo o projeto, do início ao fim, facilitando a depuração. Como precisa estar disponível a qualquer momento durante o projeto, o ThreadLocal pode ser colocado.
2. Princípio de implementação
Na classe Thread, haverá um mapa de atributo (java.lang.Thread#threadLocals),
Sempre que chamamos o método set/get de ThreadLoacl, na verdade armazenamos ou recuperamos o valor correspondente no Mapa correspondente ao thread atual, usando ThreadLoacl como chave.
Exemplo de código:
public static void main(String[] args) throws InterruptedException {
ThreadLocal<Integer> tl1 = new ThreadLocal<>();
tl1.set(1);
ThreadLocal<String> tl2 = new ThreadLocal<>();
tl2.set("main");
System.out.println("main线程:"+tl1.get());
System.out.println("main线程:"+tl2.get());
new Thread(()->{
tl1.set(2);
tl2.set("thread");
System.out.println("线程2:"+tl1.get());
System.out.println("线程2:"+tl2.get());
}).start();
Thread.sleep(100L);
System.out.println("main线程睡醒后:"+tl1.get());
System.out.println("main线程睡醒后:"+tl2.get());
}
resultado da operação:
Pode-se observar que embora dois threads operassem no mesmo objeto ThreadMap, eles não afetaram um ao outro.
3. Análise do código-fonte:
Dê uma olhada no código-fonte do ThreadLocal para ver se ele é consistente com o princípio de implementação que acabamos de mencionar.
Código-fonte do método set do ThreadLocal :
//源码中的set方法
//java.lang.ThreadLocal#set()
public void set(T value) {
//1.获取当前操作这个ThreadLoacl的线程
Thread t = Thread.currentThread();
//2.获取当前线程的属性:map
ThreadLocalMap map = getMap(t);
if (map != null)
//3—1.非空,直接对map进行设置
//此时key为threadLoacl的实例对象,value为传值
map.set(this, value);
else
//3-2.空,调用创建方法,同时设值
createMap(t, value);
}
//获取线程中的属性:java.lang.Thread#threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Código-fonte do método get do ThreadLocal :
//源码中的get方法
//java.lang.ThreadLocal#get()
public T get() {
//1.获取当前操作这个ThreadLoacl的线程
Thread t = Thread.currentThread();
//2.获取当前线程的属性:map
ThreadLocalMap map = getMap(t);
if (map != null) {
//3-1.map不为空,直接调用get方法获取value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//3-2.为空 调用创建方法,并返回null
return setInitialValue();
}
private T setInitialValue() {
//此方法返回null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
Você pode ver o código-fonte de implementação do ThreadLocal, que está alinhado com os princípios de implementação que descrevemos anteriormente.
4.ThreadLoacl e problemas de vazamento de memória
Em uma frase: Objetos inúteis que deveriam ser reciclados não são reciclados.
Resumindo, existem alguns objetos na jvm que já são lixo inútil, mas por alguns motivos, o gc não consegue limpá-los.
Razões pelas quais ThreadLoacl pode causar vazamentos de memória:
Usamos o próprio ThreadLoacl como chave. Armazenado no mapa do thread, então durante a vida útil deste thread, o objeto ThreadLoacl será mantido pelo mapa do thread.Mesmo que o objeto ThreadLoacl não seja mais usado, o gc não reciclará o objeto, por isso é muito difícil É fácil causar vazamentos de memória.
Solução própria do ThreadLoacl para vazamentos de memória
O problema que acabamos de mencionar ThreadLoacl também vem à mente, então vamos dar uma olhada na implementação interna de ThreadLoacl.ThreadLocalMap:
Como você pode ver, a Entrada do Mapa herda WeakReference, que é uma referência fraca.
WeakReference é literalmente uma referência fraca. Em outras palavras, o Entry.key salvo em ThreadLocalMap é na verdade um objeto de referência fraco (esta frase pode exigir que pessoas familiarizadas com o código-fonte do mapa entendam), e o objeto apontado pelo objeto de referência fraca está no GC quando será reciclado. (Para detalhes, veja: Explicação detalhada dos quatro tipos de referência em Java )
Combinado com o exemplo de análise de código acima
-
Se uma referência forte for usada: Quando a referência do objeto ThreadLocal (assumido como ThreadLocal@123) (ou seja: tl1, que é uma referência forte apontando para ThreadLocal@123) é reciclada, o próprio ThreadLocalMap ainda mantém a referência forte de ThreadLocal @123, Se esta chave não for excluída manualmente, ThreadLocal@123 não será reciclado, portanto, desde que o thread atual não morra, os objetos referenciados por ThreadLocalMap não serão reciclados. Isso pode ser considerado uma causa de vazamento de memória de entrada .
-
Se você usar referências fracas , haverá apenas duas referências ao objeto ThreadLocal@123: a referência forte tl1 e a referência fraca à chave em ThreadLocalMap.Entry. Depois que tl1 for reciclado, haverá apenas referências fracas apontando para ThreadLocal @ 123. Durante o próximo gc, este ThreadLocal @ 123 será reciclado.
Vendo isso, ainda temos dúvidas. O objeto chave de ThreadLocalMap.Entry é reciclado. Neste momento, a chave se torna nula, mas não podemos acessar essas Entradas. Porém, como o valor ainda está armazenado na Entrada, o valor ainda pode ocupar Se uma grande parte da memória estiver ocupada, então esta parte da memória equivale a um vazamento de memória, porque ocupa memória que não pode ser reciclada e não podemos acessá-la.
Nesse sentido, ao chamar o método de processamento ThreadLocal e chamar o método get()/set()/remove(), todos os valores com chaves nulas no thread ThreadLocalMap serão limpos. Essas ações não garantem que a memória será reciclada, porque o thread pode ser colocado de volta no pool de threads e nunca mais usado, ou seu método get()/set()/remove() pode não ser chamado quando for usado .
Portanto, devemos prestar atenção ao nosso uso diário. Quando ThreadLocalMap for determinado que não será usado, podemos chamar remove()
o método manualmente.
O possível vazamento de memória do ThreadLoacl se deve, em última análise, ao fato de que o ciclo de vida do ThreadLocalMap é tão longo quanto o Thread.