El principio de ThreadLocal, ¿cuál es la pérdida de memoria, llena de productos secos?

Principio ThreadLocal (modelo básico)

ThreadLocal Proporciona la función de variables locales de subprocesos. Su principio se puede resumir brevemente en los siguientes puntos:

  1. Cada objeto Thread mantiene un mapa con ThreadLocal como clave y cualquier tipo de objeto como valor. Este Mapa almacena los valores de las variables locales correspondientes a cada subproceso, y este Mapa se llama ThreadLocalMap.
  2. Al llamar a un método para establecer un valor de variable a través de un objeto ThreadLocal set(), se obtiene el ThreadLocalMap del subproceso actual y el objeto ThreadLocal actual se usa como clave, y el valor de la variable se usa como valor y se almacena en el mapa. .
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

3. Cuando el valor de la variable se obtiene llamando get()al método a través del objeto ThreadLocal, en realidad obtiene el ThreadLocalMap del subproceso actual y luego usa el objeto ThreadLocal actual como clave para obtener el valor correspondiente.

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

De esta manera, ThreadLocal logra el aislamiento de datos entre subprocesos, y cada subproceso puede acceder y modificar de forma independiente sus propios valores de variables locales sin afectar las variables de otros subprocesos. Esto es muy útil en la programación de subprocesos múltiples, lo que puede evitar la competencia de datos y los conflictos entre subprocesos y mejorar el rendimiento y la confiabilidad concurrentes del programa.

Transferencia de TheadLocal entre subprocesos

El valor de ThreadLocal se almacena de forma independiente en cada subproceso y otros subprocesos no pueden obtener el valor directamente. Sin embargo, puede pasar el valor de ThreadLocal de un hilo a otro de alguna manera, por ejemplo:

  1. Transferencia manual: puede obtener el valor de ThreadLocal en un hilo y luego pasar el valor como parámetro a otro hilo. Esto requiere que usted mismo administre el proceso de entrega y garantice la seguridad de los subprocesos.
  2. Cuando necesite establecer un valor ThreadLocal en el subproceso principal y hacer que esté disponible en el subproceso secundario, puede usar el objeto ThreadLocal de InheritableThreadLocaltipo . InheritableThreadLocalProporciona una manera conveniente de pasar el valor ThreadLocal entre el hilo principal y el subproceso secundario. El principio es que cuando el subproceso principal crea un subproceso secundario, el método de inicialización del subproceso pasará el valor del subproceso principal al subproceso secundario InheritableThreadLocal.

image.png 需要注意的是,使用 ThreadLocal 进行跨线程传递值时,要确保线程安全性和正确性。在传递值的过程中,需要注意值的复制或引用传递、值的可变性、值的生命周期等问题,以避免意外的副作用和错误。

总之,虽然 ThreadLocal 的值不会自动在线程间传递,但你可以通过手动传递或使用特定的 ThreadLocal 子类来实现跨线程传递值的需求。在实际应用中,请注意线程安全性和正确性的考虑。

内存泄露原因

先从WeakReference说起

众所周知,java四种引用类型,强,软,弱,虚,先搞明白WeakReference什么时候会被gc清理,网上大多数的说法都是当GC时弱引用对象会被清理,但是需要前提条件,这点很重要!!

image.png jdk文档上写的大意就是噢,如果一个对象没有强、软引用(也要根可达),只通过弱引用(根)可达,那就可以被回收。
先搞几个测试类

image.png

image.png

weakAndSick为类变量,强引用在所以没被回收掉 image.png

强引用不在所以被回收掉 image.png

局部变量表中weakAndSickInner仍存在,存在根可达的强引用,所以回收不掉

image.png

method方法结束,局部变量引用断开,可以被回收

image.png

看看ThreadLocalMap代码结构

static class ThreadLocalMap {

    /**
     * The entries in this hash map extend WeakReference, using
     * its main ref field as the key (which is always a
     * ThreadLocal object).  Note that null keys (i.e. entry.get()
     * == null) mean that the key is no longer referenced, so the
     * entry can be expunged from table.  Such entries are referred to
     * as "stale entries" in the code that follows.
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

就是说entry继承WeakReference,也就是key是弱引用类型的,key对应的是ThreadLocal对象。 所以作者在这里使用弱引用类型,就是希望在ThreadLocal对象不再被使用时,ThreadLocalMap中的key中引用的ThreadLocal也就没有意义了,不要影响对ThreadLocal对象的垃圾回收!

内存泄露是针对ThreadLocalMap的,当然如果thread是临时线程用完就丢弃了,那自然可以回收ThreadLocalMap,但是如果是线程池中的常驻内存的线程呢?作为WeakReference的ThreadLocal对象可以被回收么? 实际上我们一般都将ThreadLocal对象作为一个类变量长期使用,所以依然存在强引用,在gc时仍不能回收。如果将这个强引用断开那么ThreadLocalMap的Key就可以被回收了,但是Value依然被强引用着,不能被回收。
所以在一些特殊场景下,也会有不一样的泄露的问题。 比如一个war包被部署到Tomcat中,Tomcat会加载包中的类,然后创建Servlet\Filrer这些类实例. Tomcat维护了一个线程池,每当有请求到来,这些线程对象就会去执行用于处理请求的task,也就是Filter\Servlt这些东西. 当一个war包被卸载时,Tomcat也应该释放所加载的Class和创建的Servlet/Filter实例,使之能被GC回收。
但是

一个类被GC回收需要保证以下条件都不成立:

  1. objects of that class are still reachable.
  2. the Class object representing the class is still reachable
  3. the ClassLoader that loaded the class is still reachable
  4. other classes loaded by the ClassLoader are still reachable

而ThreadLocal还保留着对象实例,导致类对象不能卸载,也会引起内存泄露。

避免 ThreadLocal 内存泄漏

  1. 了解使用场景:使用 ThreadLocal 时要了解其适用范围和生命周期,并确保在合适的时机清理和释放相关的值,及时调用 remove() 方法。
  2. 避免过度使用 ThreadLocal:仔细评估是否真正需要使用 ThreadLocal,尽量避免过度依赖它,以减少潜在的内存泄漏风险。

总之,虽然 ThreadLocal 在多线程编程中非常有用,但需要谨慎使用和管理。正确地使用和清理 ThreadLocal 的值可以避免内存泄漏问题,确保程序的性能和稳定性。

Supongo que te gusta

Origin juejin.im/post/7235117005737427001
Recomendado
Clasificación