Explicação detalhada de ThreadLoacl (vazamento de memória causado pelo uso de + princípio + ThreadLoacl)

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),

Insira a descrição da imagem aqui

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.

Insira a descrição da imagem aqui

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:

Insira a descrição da imagem aqui

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:

Insira a descrição da imagem aqui

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.

Acho que você gosta

Origin blog.csdn.net/weixin_43828467/article/details/115394336
Recomendado
Clasificación