Comprenda el principio y los escenarios de uso de ThreadLocal en un artículo

prefacio

Ya sea trabajo o entrevista , nos ThreadLocalocuparemos de usted, hablemos con usted hoy ThreadLocal~

  1. ¿Qué es ThreadLocal?Por qué usar ThreadLocal
  2. Un caso de uso de ThreadLocal
  3. El principio de ThreadLocal
  4. ¿Por qué no usar la identificación del hilo directamente como la clave de ThreadLocalMap?
  5. ¿Por qué causa una pérdida de memoria? ¿Es por referencias débiles?
  6. ¿Por qué Key está diseñado como una referencia débil? ¿No se puede hacer una referencia fuerte?
  7. InheritableThreadLocal garantiza datos compartidos entre subprocesos principales y secundarios
  8. Escenarios de aplicación y notas para usar ThreadLocal

1. ¿Qué es ThreadLocal?¿Por qué usar ThreadLocal?

¿Qué es ThreadLocal?

ThreadLocal, una variable local de subproceso. Si crea una ThreadLocalvariable, cada subproceso que acceda a esta variable tendrá una copia local de la variable. Cuando varios subprocesos operan en esta variable, en realidad están operando en la variable en su propia memoria local, logrando así el aislamiento del subproceso. Función, evitando problemas de seguridad de subprocesos en escenarios simultáneos.

// 创建一个ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();

Por qué usar ThreadLocal

En un escenario simultáneo, habrá un escenario en el que varios hilos modifiquen una variable compartida al mismo tiempo. Esto puede presentar un problema de seguridad lineal .

Para resolver problemas de seguridad lineal, puede usar métodos de bloqueo, como usar synchronizedo Lock. Pero la forma de bloqueo puede hacer que el sistema se ralentice. El diagrama esquemático del bloqueo es el siguiente:

 

Hay otra solución, que es usar espacio por tiempo, es decir, usar ThreadLocal. Cuando se usa ThreadLocaluna clase para acceder a una variable compartida, una copia de la variable compartida se guarda localmente en cada subproceso. Cuando varios subprocesos modifican una variable compartida, en realidad operan en una copia de la variable, lo que garantiza la seguridad lineal.

 

2. Un caso de uso de ThreadLocal

En el desarrollo diario, ThreadLocala menudo aparece en la clase de herramienta de conversión de fechas. Echemos un vistazo a un contraejemplo :

/**
 * 日期工具类
 */
public class DateUtil {

    private static final SimpleDateFormat simpleDateFormat =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static Date parse(String dateString) {
        Date date = null;
        try {
            date = simpleDateFormat.parse(dateString);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }
}

Ejecutamos DateUtilesta clase de utilidad en un entorno de subprocesos múltiples:

public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.execute(()->{
                System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
            });
        }
        executorService.shutdown();
    }

Después de ejecutar, encontré un error:

Si DateUtilagrega y ejecuta en la clase de herramienta ThreadLocal, no tendrá este problema:

/**
 * 日期工具类
 */
public class DateUtil {

    private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
            ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

    public static Date parse(String dateString) {
        Date date = null;
        try {
            date = dateFormatThreadLocal.get().parse(dateString);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 10; i++) {
            executorService.execute(()->{
                System.out.println(DateUtil.parse("2022-07-24 16:34:30"));
            });
        }
        executorService.shutdown();
    }
}
复制代码

resultado de la operación:

Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
Sun Jul 24 16:34:30 GMT+08:00 2022
复制代码

En el contraejemplo de ahora , ¿por qué informa un error? Esto se debe a que SimpleDateFormatno es linealmente seguro. Cuando aparece como una variable compartida, se informará un error en un escenario de subprocesamiento múltiple concurrente.

¿Por qué no hay problema si lo agregas ThreadLocal? En el escenario concurrente, ThreadLocal¿cómo se garantiza? Veamos los principios básicos a continuación ThreadLocal.

3. El principio de ThreadLocal

3.1 Diagrama de estructura de memoria de ThreadLocal

Para tener una comprensión macro, primero veamos ThreadLocalel diagrama de estructura de memoria a continuación

Desde el diagrama de estructura de memoria, podemos ver:

  • ThreadEn la clase, hay una ThreadLocal.ThreadLocalMapvariable miembro de .
  • ThreadLocalMapUna matriz se mantiene internamente Entry, cada uno de los cuales Entryrepresenta un objeto completo, keyque es ThreadLocalen sí mismo, y valuees ThreadLocalun valor de objeto genérico.

3.2 Análisis de código fuente clave

En comparación con varios códigos fuente clave, es más fácil de entender ~ Volvamos al Threadcódigo fuente de la clase, y puede ver que ThreadLocalMapel valor inicial de la variable miembro esnull

public class Thread implements Runnable {
   // ThreadLocal.ThreadLocalMap是Thread的属性
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMapEl código fuente clave es el siguiente:

static class ThreadLocalMap {
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //Entry数组
    private Entry[] table;
    
    // ThreadLocalMap的构造器,ThreadLocal作为key
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}

ThreadLocalset()Métodos clave en la clase :

 public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程t
        ThreadLocalMap map = getMap(t);  //根据当前线程获取到ThreadLocalMap
        if (map != null)  //如果获取的ThreadLocalMap对象不为空
            map.set(this, value); //K,V设置到ThreadLocalMap中
        else
             createMap(t, value); //创建一个新的ThreadLocalMap
    }
   
     ThreadLocalMap getMap(Thread t) {
       return t.threadLocals; //返回Thread对象的ThreadLocalMap属性
    }

    void createMap(Thread t, T firstValue) { //调用ThreadLocalMap的构造函数
        t.threadLocals = new ThreadLocalMap(this, firstValue); this表示当前类ThreadLocal
    }
}
    

ThreadLocalget()método clave en la clase

    public T get() {
        Thread t = Thread.currentThread();//获取当前线程t
        ThreadLocalMap map = getMap(t);//根据当前线程获取到ThreadLocalMap
        if (map != null) { //如果获取的ThreadLocalMap对象不为空
            //由this(即ThreadLoca对象)得到对应的Value,即ThreadLocal的泛型值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value; 
                return result;
            }
        }
        return setInitialValue(); //初始化threadLocals成员变量的值
    }
    
     private T setInitialValue() {
        T value = initialValue(); //初始化value的值
        Thread t = Thread.currentThread(); 
        ThreadLocalMap map = getMap(t); //以当前线程为key,获取threadLocals成员变量,它是一个ThreadLocalMap
        if (map != null)
            map.set(this, value);  //K,V设置到ThreadLocalMap中
        else
            createMap(t, value); //实例化threadLocals成员变量
        return value;
    }

Entonces, ¿cómo responder al principio de implementación de ThreadLocal ? De la siguiente manera, es mejor explicarlo junto con el diagrama de estructura anterior ~

  • ThreadLa clase hilo tiene una ThreadLocal.ThreadLocalMapvariable de instancia de tipo threadLocals, es decir, cada hilo tiene uno propio ThreadLocalMap.
  • ThreadLocalMapUna matriz se mantiene internamente Entry, cada uno de los cuales Entryrepresenta un objeto completo, keyque es ThreadLocalen sí mismo, y valuees ThreadLocalun valor genérico.
  • En el escenario de subprocesos múltiples concurrentes, cada subproceso Thread, al establecer un valor ThreadLocalen él , ThreadLocalMaplo almacena en su propia memoria y utiliza una referencia como referencia para que cada subproceso encuentre el valor correspondiente ThreadLocalen su propia memoria , logrando así el aislamiento del subproceso .mapkey

Después de comprender estos métodos básicos, algunos amigos pueden tener dudas, ¿ ThreadLocalMappor qué deberían usarse ThreadLocalcomo claves? 线程Id¿ Es diferente usarlo directamente ?

4. ¿Por qué no usar la identificación del hilo directamente como la clave de ThreadLocalMap?

Por ejemplo, el código es el siguiente:

public class TianLuoThreadLocalTest {

    private static final ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    private static final ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
 
}

Este escenario: una clase de uso tiene dos variables compartidas, es decir, ThreadLocalse utilizan dos variables miembro. Si se idusan subprocesos , ¿cómo distinguir qué variable miembro? Por lo tanto, aún debe usarse como . Cada objeto se puede distinguir de forma única por atributos , y cada objeto ThreadLocal se puede distinguir de forma única por el nombre del objeto ( ejemplo a continuación ). Mira el código:ThreadLocalMapkeyThreadLocalThreadLocalKeyThreadLocalthreadLocalHashCodeThreadLocal

public class ThreadLocal<T> {
  private final int threadLocalHashCode = nextHashCode();
  
  private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
  }
}

Entonces veamos el siguiente ejemplo de código:

public class TianLuoThreadLocalTest {

    public static void main(String[] args) {
        Thread t = new Thread(new Runnable(){
            public void run(){
                ThreadLocal<TianLuoDTO> threadLocal1 = new ThreadLocal<>();
                threadLocal1.set(new TianLuoDTO("Hello 七度"));
                System.out.println(threadLocal1.get());
                ThreadLocal<TianLuoDTO> threadLocal2 = new ThreadLocal<>();
                threadLocal2.set(new TianLuoDTO("你好:ccc"));
                System.out.println(threadLocal2.get());
            }});
        t.start();
    }

}
// 运行结果
TianLuoDTO{name='Hello 七度'}
TianLuoDTO{name='你好:ccc'}

Puede ser más claro si lo comparas con esta imagen:

5. ¿Por qué TreadLocal provoca pérdidas de memoria?

5.1 ¿Qué pasa con las fugas de memoria causadas por referencias débiles?

Primero echemos un vistazo al diagrama de referencia de TreadLocal:

Con respecto a la fuga de memoria de ThreadLocal, la declaración más popular en Internet es esta:

ThreadLocalMapLa referencia débil utilizada como , cuando la variable se configura manualmente , es decir, no hay una referencia fuerte externa para referirse a ella, cuando el sistema GC ThreadLocaldebe reciclarse. En este caso, habrá objetos en el medio , y no hay forma de acceder a estos objetos.Si el subproceso actual no finaliza durante mucho tiempo (como el subproceso central del grupo de subprocesos), siempre habrá un fuerte cadena de referencia :estos objetosparakeyThreadLocalnullThreadLocalThreadLocalThreadLocalMapkeynullEntrykeynullEntryvaluekeynullEntryvalue

Cuando la variable ThreadLocal se establece manualmente en nullel diagrama de cadena de referencia:

De hecho, ThreadLocalMapesta situación se ha tenido en cuenta en el diseño de . Por lo tanto, también se agregan algunas medidas de protección: es decir ThreadLocal, getel setmétodo removeborrará ThreadLocalMaptodas keylas acciones en el hilo .nullvalue

En el código fuente, se refleja, como ThreadLocalMapel setmétodo:

  private void set(ThreadLocal<?> key, Object value) {

      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);

      for (Entry e = tab[i];
            e != null;
            e = tab[i = nextIndex(i, len)]) {
          ThreadLocal<?> k = e.get();

          if (k == key) {
              e.value = value;
              return;
          }

           //如果k等于null,则说明该索引位之前放的key(threadLocal对象)被回收了,这通常是因为外部将threadLocal变量置为null,
           //又因为entry对threadLocal持有的是弱引用,一轮GC过后,对象被回收。
            //这种情况下,既然用户代码都已经将threadLocal置为null,那么也就没打算再通过该对象作为key去取到之前放入threadLocalMap的value, 因此ThreadLocalMap中会直接替换调这种不新鲜的entry。
          if (k == null) {
              replaceStaleEntry(key, value, i);
              return;
          }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        //触发一次Log2(N)复杂度的扫描,目的是清除过期Entry  
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
          rehash();
    }

Como el método de ThreadLocal get:

  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //去ThreadLocalMap获取Entry,方法里面有key==null的清除逻辑
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
             return e;
        else
          //里面有key==null的清除逻辑
          return getEntryAfterMiss(key, i, e);
    }
        
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            // Entry的key为null,则表明没有外部引用,且被GC回收,是一个过期Entry
            if (k == null)
                expungeStaleEntry(i); //删除过期的Entry
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

5.2 La clave es una referencia débil, ¿el reciclaje de GC afectará el trabajo normal de ThreadLocal?

En este punto, algunos amigos pueden tener dudas, ya que es ThreadLocaluna referencia débil , ¿la GC la reciclará precipitadamente, lo que afectará el uso normal de la misma?keykeyThreadLocal

  • Referencias débiles : los objetos con referencias débiles tienen una vida útil más corta. Si un objeto solo tiene referencias débiles, el siguiente GC reciclará el objeto (independientemente de si el espacio de memoria actual es suficiente o no)

De hecho, no, porque ThreadLocal变量está referenciado, no será reciclado por GC, a menos que se elimine manualmente ThreadLocal变量设置为null, podemos ejecutar una demostración para verificarlo:

  public class WeakReferenceTest {
    public static void main(String[] args) {
        Object object = new Object();
        WeakReference<Object> testWeakReference = new WeakReference<>(object);
        System.out.println("GC回收之前,弱引用:"+testWeakReference.get());
        //触发系统垃圾回收
        System.gc();
        System.out.println("GC回收之后,弱引用:"+testWeakReference.get());
        //手动设置为object对象为null
        object=null;
        System.gc();
        System.out.println("对象object设置为null,GC回收之后,弱引用:"+testWeakReference.get());
    }
}
运行结果:
GC回收之前,弱引用:java.lang.Object@7b23ec81
GC回收之后,弱引用:java.lang.Object@7b23ec81
对象object设置为null,GC回收之后,弱引用:null

La conclusión es, amigo, deja ir esta duda, jaja~

5.3 Demostración de pérdida de memoria ThreadLocal

Déjame mostrarte el siguiente ejemplo de una pérdida de memoria, de hecho, es usar el grupo de subprocesos para seguir poniendo objetos en él.

public class ThreadLocalTestDemo {

    private static ThreadLocal<TianLuoClass> tianLuoThreadLocal = new ThreadLocal<>();


    public static void main(String[] args) throws InterruptedException {

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());

        for (int i = 0; i < 10; ++i) {
            threadPoolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("创建对象:");
                    TianLuoClass tianLuoClass = new TianLuoClass();
                    tianLuoThreadLocal.set(tianLuoClass);
                    tianLuoClass = null; //将对象设置为 null,表示此对象不在使用了
                   // tianLuoThreadLocal.remove();
                }
            });
            Thread.sleep(1000);
        }
    }

    static class TianLuoClass {
        // 100M
        private byte[] bytes = new byte[100 * 1024 * 1024];
    }
}


创建对象:
创建对象:
创建对象:
创建对象:
Exception in thread "pool-1-thread-4" java.lang.OutOfMemoryError: Java heap space
	at com.example.dto.ThreadLocalTestDemo$TianLuoClass.<init>(ThreadLocalTestDemo.java:33)
	at com.example.dto.ThreadLocalTestDemo$1.run(ThreadLocalTestDemo.java:21)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

OOM apareció en el resultado de la ejecución, tianLuoThreadLocal.remove();pero después de agregarlo, no lo hará OOM.

创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
创建对象:
......
复制代码

No configuramos manualmente la tianLuoThreadLocalvariable aquí null, pero aún habrá una pérdida de memoria . Debido a que usamos el grupo de subprocesos, el grupo de subprocesos tiene un ciclo de vida largo, por lo que el grupo de subprocesos siempre tendrá el valor tianLuoClassdel objeto value, incluso si la tianLuoClass = null;referencia establecida todavía existe. Es como poner cada objeto objecten una listlista y luego configurarlo por separado object, nullla razón es la misma, el objeto de la lista todavía existe.

    public static void main(String[] args) {
        List<Object> list = new ArrayList<>();
        Object object = new Object();
        list.add(object);
        object = null;
        System.out.println(list.size());
    }
    //运行结果
    1

Así que la fuga de memoria ocurrió así, y finalmente la memoria se limitó, por lo que se descartó OOM. Si lo agregamos threadLocal.remove();, no habrá pérdida de memoria. ¿por qué? Debido a threadLocal.remove();que se borrará Entry, el código fuente es el siguiente:

    private void remove(ThreadLocal<?> key) {
      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);
      for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
          if (e.get() == key) {
              //清除entry
              e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

Algunos amigos dijeron que, dado que las fugas de memoria no se deben necesariamente a referencias débiles, ¿por qué deben diseñarse como referencias débiles? Vamos a explorar:

6. ¿Por qué debe diseñarse la Clave de entrada como una referencia débil?

A través del código fuente, podemos ver Entryque Keyestá diseñado como una referencia débil ( la referencia débil utilizada ThreadLocalMap) . ¿Por qué está diseñado como una referencia débil?ThreadLocalKey

 

Recordemos primero las cuatro referencias:

  • Referencia fuerte : por lo general, newsabemos que un objeto es una referencia fuerte. Por ejemplo, Object obj = new Object();incluso en el caso de memoria insuficiente, la JVM preferiría arrojar un error OutOfMemory que reciclar este objeto.
  • Referencias blandas : si un objeto solo tiene referencias blandas, el espacio de memoria es suficiente y el recolector de basura no lo reclamará; si el espacio de memoria es insuficiente, se reclamará la memoria de estos objetos.
  • Referencias débiles : los objetos con referencias débiles tienen una vida útil más corta. Si solo existen referencias débiles para un objeto, el siguiente GC reciclará el objeto (independientemente de si el espacio de memoria actual es suficiente o no).
  • Referencias fantasma : si un objeto contiene solo referencias fantasma, es como si no tuviera referencias y el recolector de basura puede reclamarlo en cualquier momento. Las referencias fantasma se utilizan principalmente para realizar un seguimiento de la actividad de los objetos reclamados por el recolector de elementos no utilizados.

Primero echemos un vistazo a la documentación oficial, ¿por qué debería diseñarse como una referencia débil?

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

Permítanme mover el diagrama de referencia de ThreadLocal:

 

Analicemos por situación:

  • Si Keyusa referencias seguras: cuando ThreadLocalel objeto se recicla, pero ThreadLocalMapaún tiene ThreadLocaluna referencia segura, si no se elimina manualmente, ThreadLocal no se reciclará y se producirá el problema de pérdida de memoria de Entry.
  • Si Keyusa referencias débiles: cuando ThreadLocalel objeto se recicla, debido a ThreadLocalMapque contiene una referencia débil a ThreadLocal, ThreadLocal se reciclará incluso si no se elimina manualmente. Se borrará valueen la próxima ThreadLocalMapllamada .set,get,remove

EntryPor lo tanto, se puede encontrar que el uso de referencias débiles Keypuede proporcionar una capa adicional de protección: las referencias débiles ThreadLocalno perderán memoria fácilmente y las correspondientes se borrarán valueen la siguiente ThreadLocalMapllamada .set,get,remove

De hecho, la causa raíz de nuestra pérdida de memoria fue que la memoria que ya no se usaba Entryno se eliminó del subproceso ThreadLocalMap. EntryGeneralmente, hay dos formas de eliminar los que ya no se usan :

  • Una es, después de usarlo ThreadLocal, llamarlo manualmente remove()y Entry从ThreadLocalMapeliminarlo
  • Otra forma es: ThreadLocalMapel mecanismo de compensación automática para borrar la caducidad Entry( ThreadLocalMapcada get(),set()vez que se active Entryla compensación de la caducidad)

7. InheritableThreadLocal garantiza datos compartidos entre subprocesos principales y secundarios

Sabemos ThreadLocalque los subprocesos están aislados, si queremos que los subprocesos principales y secundarios compartan datos, ¿cómo hacerlo? InheritableThreadLocalse puede utilizar Echemos un vistazo primero demo:

public class InheritableThreadLocalTest {

   public static void main(String[] args) {
       ThreadLocal<String> threadLocal = new ThreadLocal<>();
       InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

       threadLocal.set("你好,七度");
       inheritableThreadLocal.set("你好,七度");

       Thread thread = new Thread(()->{
           System.out.println("ThreadLocal value " + threadLocal.get());
           System.out.println("InheritableThreadLocal value " + inheritableThreadLocal.get());
       });
       thread.start();
       
   }
}
// 运行结果
ThreadLocal value null
InheritableThreadLocal value 你好,七度

Se puede encontrar que en el subproceso secundario, se puede obtener el valor de InheritableThreadLocal la variable de tipo , pero ThreadLocal no se puede obtener el valor de la variable de tipo.

Podemos entender que el valor del tipo no se puede obtener ThreadLocal , porque está aislado por subprocesos. InheritableThreadLocal ¿Cómo se hace? ¿Cuál es el principio?

En Threaduna clase, además de las variables miembro threadLocals, existe otra variable miembro: inheritableThreadLocals. Estos dos tipos son iguales:

public class Thread implements Runnable {
   ThreadLocalMap threadLocals = null;
   ThreadLocalMap inheritableThreadLocals = null;
 }

ThreadEn el método de la clase init, hay una sección de configuración de inicialización:

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
      
        ......
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

Se puede encontrar que cuando parent的inheritableThreadLocalsno está nullhecho , será parentasignado inheritableThreadLocalsal hilo anterior inheritableThreadLocals. Para decirlo sin rodeos, si el subproceso actual inheritableThreadLocalsno hace nada null, copie uno del subproceso principal, similar al otro ThreadLocal, pero los datos provienen del subproceso principal. Los amigos interesados ​​pueden estudiar el código fuente ~

8. Escenarios de aplicación y notas para usar ThreadLocal

ThreadLocalUn punto muy importante a tener en cuenta es que debe llamarse manualmente después de su uso remove().

Los ThreadLocalescenarios de aplicación incluyen principalmente lo siguiente:

  • Use la clase de herramienta de fecha, cuando se use SimpleDateFormat, use ThreadLocal para garantizar la seguridad lineal
  • Almacenar globalmente la información del usuario (la información del usuario se almacena ThreadLocal, por lo que el subproceso actual puede usarla en cualquier lugar que la necesite)
  • Garantice el mismo hilo, la conexión de la base de datos obtenida Connectiones la misma, utilícela ThreadLocalpara resolver el problema de la seguridad del hilo
  • Úselo MDCpara guardar información de registro.

Supongo que te gusta

Origin blog.csdn.net/gongzi_9/article/details/126754648
Recomendado
Clasificación