La explicación ThreadLocal más completa de la historia (1)

Tabla de contenido

1. Introducción a ThreadLocal

2. La diferencia entre ThreadLocal y Sincronizado

3. Uso sencillo de ThreadLocal

4. El principio de ThreadLocal

        4.1 Método set() de ThreadLocal:

         4.2 Método get de ThreadLocal

4.3 Eliminar método de ThreadLocal

 4.4 La relación entre ThreadLocal y Thread, ThreadLocalMap  

5. Escenarios de uso comunes de ThreadLocal


1. Introducción a ThreadLocal

ThreadLocal se llama variable de subproceso , lo que significa que la variable completada en ThreadLocal pertenece al subproceso actual . Esta variable está aislada de otros subprocesos, lo que significa que la variable es única para el subproceso actual. ThreadLocal crea una copia de la variable en cada hilo, por lo que cada hilo puede acceder a su propia variable de copia interna.

Las variables ThreadLoal, las variables locales de subprocesos y los objetos contenidos en el mismo ThreadLocal tienen diferentes copias en diferentes subprocesos. Aquí hay algunas cosas a tener en cuenta:

  • Porque cada Thread tiene su propia copia de la instancia, y esta copia solo puede ser utilizada por el Thread actual . Este es también el origen del nombre ThreadLocal.
  • Dado que cada subproceso tiene su propia copia de instancia y es inaccesible para otros subprocesos, no hay problema de compartir entre varios subprocesos .

ThreadLocal proporciona instancias locales de subprocesos. Se diferencia de una variable normal en que cada hilo que utiliza la variable inicializa una copia completamente independiente de la instancia. Las variables ThreadLocal suelen estar decoradas con estática privada. Cuando un hilo termina, todas las copias de instancias relativas a ThreadLocal que utilizó se pueden reciclar.

En general, ThreadLocal es adecuado para escenarios en los que cada subproceso necesita su propia instancia independiente y la instancia debe usarse en múltiples métodos, es decir, las variables están aisladas entre subprocesos pero compartidas entre métodos o clases.

La siguiente imagen puede mejorar la comprensión:


                                                                 Figura 1-1 Estado de ThreadLocal durante el uso


2. La diferencia entre ThreadLocal y Sincronizado

ThreadLocal<T> es en realidad una variable vinculada al hilo. Tanto ThreadLocal como Synchonized se utilizan para resolver el acceso concurrente de subprocesos múltiples.

Pero existen diferencias esenciales entre ThreadLocal y sincronizado:

1. Sincronizado se usa para compartir datos entre subprocesos, mientras que ThreadLocal se usa para aislar datos entre subprocesos.

2. Sincronizado utiliza el mecanismo de bloqueo para que solo un subproceso pueda acceder a las variables o bloques de código en un momento determinado. ThreadLocal proporciona una copia de la variable para cada hilo.

, De modo que cada subproceso no acceda al mismo objeto en un momento determinado, aislando así el intercambio de datos entre múltiples subprocesos.

Sincronizado es todo lo contrario: se utiliza para compartir datos cuando se comunican entre múltiples subprocesos.

Para entender ThreadLocal en una oración, threadlocl es la entrada de valor clave (threadlocl, valor) de una entrada en la colección de atributos ThreadLocalMap del hilo actual. Aunque el valor de la clave threadlocal es el mismo entre diferentes subprocesos, el ThreadLocalMap propiedad de diferentes subprocesos. es único, es decir, el valor (valor) almacenado en el mismo ThreadLocal (clave) es diferente entre diferentes subprocesos, logrando así el propósito de aislamiento de variables entre subprocesos, pero la dirección de la variable de valor es la misma en el mismo subproceso.

3. Uso sencillo de ThreadLocal

Vaya directamente al código:

public class ThreadLocaDemo {

    private static ThreadLocal<String> localVar = new ThreadLocal<String>();

    static void print(String str) {
        //打印当前线程中本地内存中本地变量的值
        System.out.println(str + " :" + localVar.get());
        //清除本地内存中的本地变量
        localVar.remove();
    }
    public static void main(String[] args) throws InterruptedException {

        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_A");
                print("A");
                //打印本地变量
                System.out.println("after remove : " + localVar.get());
               
            }
        },"A").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                ThreadLocaDemo.localVar.set("local_B");
                print("B");
                System.out.println("after remove : " + localVar.get());
              
            }
        },"B").start();
    }
}

A :local_A
after remove : null
B :local_B
after remove : null

En este ejemplo, podemos ver que las dos subtablas de subprocesos obtienen las variables almacenadas en sus propios subprocesos, y la adquisición de variables entre ellas no será confusa. Esta comprensión también se puede combinar con la Figura 1-1, creo que habrá una comprensión más profunda.

4. El principio de ThreadLocal

Para ver el principio, debes comenzar con el código fuente.

  4.1 Método set() de ThreadLocal:

 public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
    }

         Como se puede ver en el código anterior, al asignar un valor al conjunto ThreadLocal, primero obtendrá el subproceso actual y obtendrá el atributo ThreadLocalMap en el subproceso. Si el atributo del mapa no está vacío, el valor se actualiza directamente. Si el mapa está vacío, se crea una instancia de threadLocalMap y se inicializa el valor.

Entonces, ¿qué es ThreadLocalMap y cómo lo hace createMap? Sigamos leyendo. Al final, todos tendrán una comprensión más profunda siguiendo el código fuente de la idea.

  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;
            }
        }

        
    }

    Se puede ver que ThreadLocalMap es una clase estática interna de ThreadLocal, y su composición utiliza principalmente Entry para guardar datos, y también es una referencia débil heredada. Utilice ThreadLocal como clave dentro de la Entrada y utilice el valor que establecemos como valor. Tienes que seguir los detalles tú mismo.

//这个是threadlocal 的内部方法
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }


    //ThreadLocalMap 构造方法
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);
        }

         4.2 Método get de ThreadLocal

      

    public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据不为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
    }


private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

4.3 Eliminar método de ThreadLocal

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

 El método remove elimina directamente el valor correspondiente a ThrealLocal del ThreadLocalMap en el Thread de diferencia actual. ¿Por qué deberíamos eliminarlo? Esto implica pérdidas de memoria.

De hecho, la clave utilizada en ThreadLocalMap es una referencia débil a ThreadLocal. La característica de las referencias débiles es que si este objeto solo tiene referencias débiles, definitivamente se limpiará durante la próxima recolección de basura.

Por lo tanto, si el exterior no hace referencia fuerte a ThreadLocal, se limpiará durante la recolección de basura, de esta manera, la clave de ThreadLocal utilizada en ThreadLocalMap también se limpiará. Sin embargo, el valor es una referencia fuerte y no se limpiará, por lo que habrá un valor con una clave nula.

ThreadLocal es en realidad una variable vinculada al hilo, por lo que surgirá un problema: si la variable en ThreadLocal no se elimina (elimina) o reemplaza, su ciclo de vida coexistirá con el hilo. Por lo general, la gestión de subprocesos en grupos de subprocesos utiliza la reutilización de subprocesos. En los grupos de subprocesos, los subprocesos son difíciles de finalizar o incluso nunca finalizan, lo que significa que la duración del subproceso será impredecible e incluso consistente con el ciclo de vida de la JVM. Por ejemplo, si una clase de colección u objeto complejo se empaqueta directa o indirectamente en ThreadLocal, y cada vez que el objeto se extrae del mismo ThreadLocal y luego se manipula el contenido, el espacio ocupado por la clase de colección interna y el objeto complejo puede aumentar. .Comienza a expandirse.

 4.4 La relación entre ThreadLocal y Thread, ThreadLocalMap  

Figura 4-1 Diagrama de relación de datos entre Thread, THreadLocal y ThreadLocalMap

En esta imagen, podemos ver muy intuitivamente que ThreadLocalMap es en realidad un valor de atributo del hilo Thread, y ThreadLocal mantiene ThreadLocalMap.

Este atributo se refiere a una clase de herramienta. Los subprocesos pueden tener múltiples variables compartidas exclusivas de su propio subproceso mantenido por ThreadLocal (esta variable compartida solo se comparte dentro de su propio subproceso)

5. Escenarios de uso comunes de ThreadLocal

Como se mencionó anteriormente, ThreadLocal es adecuado para los dos escenarios siguientes

  • 1. Cada hilo debe tener su propia instancia separada.
  • 2. La instancia debe compartirse entre varios métodos, pero no quiere que varios subprocesos la compartan.

Para el primer punto, cada hilo tiene su propia instancia y hay muchas formas de implementarla. Por ejemplo, se puede construir una instancia separada dentro de un hilo. ThreadLoca satisface esta necesidad de una forma muy conveniente.

Para el segundo punto, se puede lograr mediante la transferencia de referencia entre métodos bajo la condición de que se cumpla el primer punto (cada hilo tiene su propia instancia). ThreadLocal hace que el código esté menos acoplado y la implementación sea más elegante.

Escenas

1) Guardar sesión de usuario

Un ejemplo simple del uso de ThreadLocal para almacenar la sesión:

private static final ThreadLocal threadSession = new ThreadLocal();

    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    }

Escenario 2, conexión de base de datos, procesamiento de transacciones de base de datos

Escenario 3: transferencia de datos entre capas (controlador, servicio, dao)

      Cada subproceso necesita guardar información similar a las variables globales (como la información del usuario obtenida en el interceptor), que puede usarse directamente mediante diferentes métodos para evitar el problema del paso de parámetros, pero no desea que varios subprocesos la compartan (porque el usuario información obtenida por diferentes hilos La información es diferente).

Por ejemplo, use ThreadLocal para guardar algún contenido comercial (información de permiso del usuario, nombre de usuario obtenido del sistema del usuario, ID de usuario, etc.). Esta información es la misma en el mismo hilo, pero el contenido comercial utilizado por diferentes hilos es diferente. .

Durante el ciclo de vida del subproceso, el objeto que se ha configurado usted mismo se obtiene a través del método get () de esta instancia estática de ThreadLocal, evitando la molestia de pasar este objeto (como el objeto de usuario) como parámetro.

Por ejemplo, si somos un sistema de usuario, cuando llega una solicitud, un hilo será responsable de ejecutar la solicitud, y luego la solicitud llamará a servicio-1 (), servicio-2 (), servicio-3 (), servicio en secuencia -4(), estos 4 métodos pueden distribuirse en diferentes clases. Este ejemplo es algo similar al almacenamiento de sesiones.

package com.kong.threadlocal;


public class ThreadLocalDemo05 {
    public static void main(String[] args) {
        User user = new User("jack");
        new Service1().service1(user);
    }

}

class Service1 {
    public void service1(User user){
        //给ThreadLocal赋值,后续的服务直接通过ThreadLocal获取就行了。
        UserContextHolder.holder.set(user);
        new Service2().service2();
    }
}

class Service2 {
    public void service2(){
        User user = UserContextHolder.holder.get();
        System.out.println("service2拿到的用户:"+user.name);
        new Service3().service3();
    }
}

class Service3 {
    public void service3(){
        User user = UserContextHolder.holder.get();
        System.out.println("service3拿到的用户:"+user.name);
        //在整个流程执行完毕后,一定要执行remove
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {
    //创建ThreadLocal保存User对象
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class User {
    String name;
    public User(String name){
        this.name = name;
    }
}

执行的结果:

service2拿到的用户:jack
service3拿到的用户:jack

Escenario 4: Spring usa ThreadLocal para resolver problemas de seguridad de subprocesos 

Sabemos que, en general, solo los beans sin estado se pueden compartir en un entorno de subprocesos múltiples. En Spring, la mayoría de los beans se pueden declarar como alcance singleton. Esto se debe a que Spring usa ThreadLocal para encapsular los "objetos con estado" no seguros para subprocesos en algunos beans (como RequestContextHolder, TransactionSynchronizationManager, LocaleContextHolder, etc.), lo que los convierte también en "objetos con estado" seguros para subprocesos, por lo que los beans con estado pueden Funciona normalmente en subprocesos múltiples en modo singleton. 

Las aplicaciones web generales se dividen en tres niveles: capa de presentación, capa de servicio y capa de persistencia. La lógica correspondiente se escribe en diferentes capas y la capa inferior abre llamadas de función a la capa superior a través de interfaces. En circunstancias normales, todas las llamadas al programa desde la recepción de una solicitud hasta la devolución de una respuesta pertenecen al mismo hilo, como se muestra en la Figura 9-2. 
 


 

De esta manera, los usuarios pueden almacenar algunas variables no seguras para subprocesos en ThreadLocal según sea necesario. En el subproceso de llamada de la misma respuesta de solicitud, la misma variable ThreadLocal a la que acceden todos los objetos está vinculada al subproceso actual.

El siguiente ejemplo puede reflejar la idea de Spring de transformar beans con estado:


 

Listado de Código 9-5 TopicDao: No es seguro para subprocesos


public class TopicDao {
   //①一个非线程安全的变量
   private Connection conn; 
   public void addTopic(){
        //②引用非线程安全变量
	   Statement stat = conn.createStatement();
	   …
   }

Dado que conn en ① es una variable miembro, debido a que el método addTopic() no es seguro para subprocesos, se debe crear una nueva instancia de TopicDao (no un singleton) cuando se usa. Lo siguiente utiliza ThreadLocal para transformar el "estado" de conn que no es seguro para subprocesos: 

Listado de Código 9-6 TopicDao: Seguridad de subprocesos 


import java.sql.Connection;
import java.sql.Statement;
public class TopicDao {
 
  //①使用ThreadLocal保存Connection变量
private static ThreadLocal<Connection> connThreadLocal = new ThreadLocal<Connection>();
public static Connection getConnection(){
         
	    //②如果connThreadLocal没有本线程对应的Connection创建一个新的Connection,
        //并将其保存到线程本地变量中。
if (connThreadLocal.get() == null) {
			Connection conn = ConnectionManager.getConnection();
			connThreadLocal.set(conn);
              return conn;
		}else{
              //③直接返回线程本地变量
			return connThreadLocal.get();
		}
	}
	public void addTopic() {
 
		//④从ThreadLocal中获取线程对应的
         Statement stat = getConnection().createStatement();
	}

Cuando diferentes subprocesos usan TopicDao, primero determine si connThreadLocal.get () es nulo. Si es nulo, significa que el subproceso actual no tiene un objeto de conexión correspondiente. En este momento, cree un objeto de conexión y agréguelo al local. variable de hilo; si no es nulo, significa que el hilo actual ya posee el objeto Conexión y puede usarse directamente. De esta manera, se garantiza que diferentes subprocesos utilicen conexiones relacionadas con subprocesos y no utilizarán conexiones de otros subprocesos. Por lo tanto, este TopicDao puede lograr el intercambio único. 

Por supuesto, este ejemplo en sí es muy aproximado: colocar el ThreadLocal de Connection directamente en Dao solo puede evitar problemas de seguridad de subprocesos cuando varios métodos de este Dao comparten Connection, pero no puede compartir la misma Connection con otros Dao para lograr la misma transacción. Daos comparte la misma conexión y debe usar ThreadLocal para guardar la conexión en una clase externa común. Pero este ejemplo básicamente ilustra la solución de Spring para la seguridad de subprocesos de clases con estado. Más adelante en este capítulo, explicaremos en detalle cómo Spring resuelve el problema de la gestión de transacciones a través de ThreadLocal.

Se agregará más tarde

Artículo siguiente: Explicación detallada del ThreadLocal más completo de la historia (2)

Ayúdenme a leer este artículo: Comprender AQS en un artículo, AQS desde la entrada hasta la lectura del código fuente

referencia:

Escenarios de aplicación de ThreadLocal - sw_kong - Blog Park

Verificación de seguridad de Baidu

Análisis de escenarios de uso de ThreadLocal: libro breve

Análisis de principios de ThreadLocal y escenarios de uso-Afanlu-Blog Park

Análisis de principios de ThreadLocal y escenarios de uso-Afanlu-Blog Park

Análisis de escenarios de uso de ThreadLocal: libro breve

(Transferencia) El uso de ThreadLocal en el Blog-CSDN de spring_Confused Desire Blog_spring threadlocal

Supongo que te gusta

Origin blog.csdn.net/u010445301/article/details/111322569
Recomendado
Clasificación