Explicación detallada de ThreadLoacl (pérdida de memoria causada por el uso de + principio + ThreadLoacl)

1. ¿Qué es ThreadLocal?

ThreadLocal, localización de subprocesos, puede privatizar el valor variable del subproceso almacenado. Se puede entender simplemente que ThreadLoacl puede proporcionar a cada subproceso su propio espacio de almacenamiento, al que se puede acceder en cualquier momento durante todo el proceso de supervivencia del subproceso, lo que facilita enormemente la implementación de cierta lógica.

Los usos comunes incluyen:

  • Almacena información de contexto de subprocesos individuales. Por ejemplo, almacenar ID, etc.;
  • Haga que las variables sean seguras para subprocesos. Dado que las variables se convierten en variables locales dentro de cada hilo, naturalmente no habrá problemas de concurrencia;
  • Reducir el paso de parámetros. Por ejemplo, cree una herramienta de seguimiento que pueda generar toda la información de todo el proyecto desde el principio hasta el final, lo que facilitará la depuración. Dado que debe estar disponible en cualquier momento durante todo el proyecto, se puede colocar ThreadLocal.

2. Principio de implementación

En la clase Thread, habrá un atributo Map (java.lang.Thread#threadLocals),

Insertar descripción de la imagen aquí

Siempre que llamamos al método set/get de ThreadLoacl, en realidad almacenamos o recuperamos el valor correspondiente en el mapa correspondiente al hilo actual, usando ThreadLoacl como clave.

Insertar descripción de la imagen aquí

Ejemplo 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 de la operación:

Insertar descripción de la imagen aquí

Se puede ver que aunque dos subprocesos operan en el mismo objeto ThreadMap, no se afectan entre sí.

3. Análisis del código fuente:

Eche un vistazo al código fuente de ThreadLocal para ver si es coherente con el principio de implementación que acabamos de mencionar.

Código fuente del método set de 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 fuente del método get de 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;
}

Puede ver el código fuente de implementación de ThreadLocal, que está en línea con los principios de implementación que describimos anteriormente.

4.ThreadLoacl y problemas de pérdida de memoria

En una frase: los objetos inútiles que deberían reciclarse no se reciclan.

En resumen, hay algunos objetos en la jvm que ya son basura inútil, pero por alguna razón, gc no puede borrarlos.

Razones por las que ThreadLoacl puede provocar pérdidas de memoria:

Usamos el propio ThreadLoacl como clave. Almacenado en el mapa del subproceso, durante la vida útil de este subproceso, el objeto ThreadLoacl será retenido por el mapa del subproceso. Incluso si el objeto ThreadLoacl ya no se usa, gc no reciclará el objeto, por lo que es muy difícil Es fácil provocar pérdidas de memoria.

La propia solución de ThreadLoacl para las pérdidas de memoria

También me viene a la mente el problema que acabo de mencionar ThreadLoacl, así que veamos la implementación interna de ThreadLoacl.ThreadLocalMap:

Insertar descripción de la imagen aquí

Como puede ver, la entrada del mapa hereda WeakReference, que es una referencia débil.

WeakReference es literalmente una referencia débil. En otras palabras, el Entry.key guardado en ThreadLocalMap es en realidad un objeto de referencia débil (esta oración puede requerir que personas familiarizadas con el código fuente del mapa la comprendan), y el objeto al que apunta el objeto de referencia débil está en el GC cuando será reciclado. (Para más detalles, consulte: Explicación detallada de los cuatro tipos de referencia en Java )

Combinado con el análisis de código de ejemplo anterior

  • Si se utiliza una referencia fuerte: cuando se recicla la referencia del objeto ThreadLocal (que se supone es ThreadLocal@123) (es decir, tl1, que es una referencia fuerte que apunta a ThreadLocal@123), el propio ThreadLocalMap aún mantiene la referencia fuerte de ThreadLocal. @123, si esta clave no se elimina manualmente, ThreadLocal@123 no se reciclará, por lo que mientras el hilo actual no muera, los objetos a los que hace referencia ThreadLocalMap no se reciclarán. Se puede considerar que esto causa una pérdida de memoria de entrada .

  • Si usa referencias débiles , solo hay dos referencias al objeto ThreadLocal@123: la referencia fuerte tl1 y la referencia débil a la clave en ThreadLocalMap.Entry. Una vez que se recicla tl1, solo habrá referencias débiles que apunten a ThreadLocal@123. Durante el próximo gc, este ThreadLocal@123 se reciclará.

Al ver esto, todavía tenemos preguntas. El objeto clave de ThreadLocalMap.Entry se recicla. En este momento, la clave se vuelve nula, pero no podemos acceder a estas entradas. Sin embargo, dado que el valor todavía está almacenado en la entrada, el valor aún puede ocupar Si una gran parte de la memoria está ocupada, entonces esta parte de la memoria equivale a una pérdida de memoria, porque ocupa memoria que no se puede reciclar y no podemos acceder a ella.

En este sentido, al llamar al método de procesamiento ThreadLocal y llamar al método get()/set()/remove(), se borrarán todos los valores con claves nulas en el hilo ThreadLocalMap. Estas acciones no pueden garantizar que la memoria se reciclará, porque el subproceso puede volver a colocarse en el grupo de subprocesos y nunca volver a usarse, o es posible que no se llame a su método get()/set()/remove() cuando se usa.
Por lo tanto, debemos prestar atención a nuestro uso diario: cuando se determina que ThreadLocalMap no se utiliza, podemos llamar manualmente remove()al método.

La posible pérdida de memoria de ThreadLoacl se debe en última instancia al hecho de que el ciclo de vida de ThreadLocalMap es tan largo como el de Thread.

Supongo que te gusta

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