Habla sobre la comprensión de ThreadLocal

 En el módulo de múltiples subprocesos de Java, ThreadLocal es un punto de conocimiento que se pregunta a menudo. Hay muchas formas de hacer preguntas. Puede ser un paso a paso o puede ser como mi tema. Por lo tanto, solo un comprensión, no importa cómo preguntes, todo puede hacerlo bien.

 Este artículo analiza y comprende principalmente desde las siguientes perspectivas

 1. ¿Qué es ThreadLocal?

 2. Cómo usar ThreadLocal

 3. Análisis de código fuente ThreadLocal

 4. Problema de pérdida de memoria ThreadLocal


¿Qué es ThreadLocal?

 Por el nombre, podemos ver que ThreadLocal se llama una variable de subproceso, lo que significa que la variable rellenada en ThreadLocal pertenece al subproceso actual y la variable está aislada de otros subprocesos. ThreadLocal crea una copia de la variable en cada hilo, por lo que cada hilo puede acceder a su propia variable de copia interna.

 Es muy fácil de entender desde el significado literal, pero desde el punto de vista del uso real, no es tan fácil. Como se pregunta con frecuencia en una entrevista, los escenarios de uso también son bastante ricos:

1. Al pasar objetos a través de capas, el uso de ThreadLocal puede evitar múltiples pasadas y romper las restricciones entre capas.
2. Aislamiento de datos entre subprocesos
3. Realice operaciones de transacción para almacenar información de transacciones de subprocesos.
4. Conexión a la base de datos, gestión de sesiones de sesión.

¿Cómo usar ThreadLocal?

Dado que el rol de ThreadLocal es crear una copia para cada hilo, usamos un ejemplo para verificar:

    public static void main(String[] args) {
    
    

        //新建一个threadLocal
        ThreadLocal<String>local = new ThreadLocal<>();
        //新建一个随机数
        Random random = new Random();
        //利用java8的stream新建5个线程
        IntStream.range(0,5).forEach(a -> new Thread(() -> {
    
    
            //为每一个线程设置相应的local值
            local.set(a+ "  " + random.nextInt(10) );
            System.out.println("线程和local值分别是: "+ local.get());
            try{
    
    
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
    
    
                e.printStackTrace();
            }
        }).start() );
    }
//        线程和local值分别是: 0  0
//        线程和local值分别是: 3  0
//        线程和local值分别是: 4  2
//        线程和local值分别是: 2  6
//        线程和local值分别是: 1  3

 A partir de los resultados, podemos ver que cada hilo tiene su propio valor local. Establecemos un tiempo de suspensión para que otro hilo pueda leer el valor local actual en el tiempo.

 Este es el uso básico de TheadLocal, ¿es muy simple? Entonces, ¿por qué se usa más cuando se conecta a la base de datos?

Cuando usamos la base de datos, primero establecemos una conexión a la base de datos y luego la cerramos cuando se nos acaba. Esto tiene un problema muy serio. Si un cliente usa frecuentemente la base de datos, entonces necesitamos establecer múltiples conexiones y cerrarla. Nuestro servidor puede estar abrumado, ¿qué debemos hacer? Si hay 10.000 clientes, la presión del servidor es aún mayor.

ThreadLocal es mejor en este momento, porque ThreadLocal crea una copia de la conexión en cada subproceso, y se puede usar en cualquier lugar dentro del subproceso, y los subprocesos no se afectan entre sí, por lo que no hay ningún problema de seguridad de subprocesos, y no lo hará. Afectar seriamente el desempeño de ejecución del programa. ¿Es muy útil?

Lo anterior es principalmente para explicar un caso básico y luego analizar por qué se usa ThreadLocal al conectarse a la base de datos. Analicemos el principio de funcionamiento de ThreadLocal desde la perspectiva del código fuente.

Análisis de código fuente ThreadLocal

En el primer ejemplo, solo se dan dos métodos, a saber, métodos get y set. De hecho, hay algunos más que necesitan nuestra atención.

  1. establecer método

     /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
          
          
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    Desde el método set, podemos ver que el hilo actual t se obtiene primero, y luego se llama a getMap para obtener el ThreadLocalMap. Si el mapa existe, el objeto del hilo actual t se usa como clave y el objeto que se va a almacenar es almacenado en el mapa como el valor. Si el mapa no existe, inicialice uno.

    De acuerdo, en este punto, creo que tendrá algunas dudas sobre qué es ThreadLocalMap y cómo se implementa el método getMap. Con estas preguntas, continúe mirando hacia abajo. Primero mire 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;
            }
        }
    

    Podemos ver que ThreadLocalMap es en realidad una clase interna estática de ThreadLocal, que define una entrada para guardar datos, y también es una referencia débil heredada. Use ThreadLocal como clave dentro de Entry y use el valor que establecimos como valor.

    También hay un getMap

    ThreadLocalMap getMap(Thread t) {
          
          
    
    return t.threadLocals;
    
    }
    
    

    Llame al hilo actual t y devuelva la variable miembro threadLocals en el hilo actual t. Y threadLocals es en realidad ThreadLocalMap.

  2. obtener método

        /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }
    

    A través de la introducción de ThreadLocal anterior, creo que tiene una buena comprensión de este método. Primero obtenga el hilo actual y luego llame al método getMap para obtener un ThreadLocalMap. Si el mapa no es nulo, use el hilo actual como entrada clave de ThreadLocalMap, y luego el valor Como valor correspondiente, si no, establezca un valor inicial.

    ¿Cómo establecer un valor inicial?

        /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    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;
    }
    
  3. El método de eliminación
    se puede eliminar de nuestro mapa.

        /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
          
          
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
    

(1) Cada hilo mantiene una referencia a ThreadLocalMap

(2) ThreadLocalMap es una clase interna de ThreadLocal, que usa Entry para almacenar

(3) La copia creada por ThreadLocal se almacena en su propio threadLocals, que es su propio ThreadLocalMap.

(4) El valor clave de ThreadLocalMap es un objeto ThreadLocal, y puede haber varias variables threadLocal, por lo que se almacenan en el mapa

(5) Antes de hacer get, primero debe establecer, de lo contrario se informará una excepción de puntero nulo. Por supuesto, también se puede inicializar uno, pero el método initialValue () debe reescribirse.

(6) ThreadLocal en sí mismo no almacena el valor, solo sirve como una clave para permitir que el hilo obtenga el valor del ThreadLocalMap.

Varios otros puntos a tener en cuenta en ThreadLocal

Inserte la descripción de la imagen aquí
Cualquier artículo que presente ThreadLocal lo ayudará a comprender un punto, es decir, el problema de la pérdida de memoria. Primero veamos la imagen de abajo.

La imagen de arriba revela la relación entre ThreadLocal y Thread y ThreadLocalMap en detalle.

1. Hay un mapa en Thread, que es ThreadLocalMap

2. La clave de ThreadLocalMap es ThreadLocal y el valor lo establecemos nosotros mismos.

3. ThreadLocal es una referencia débil, cuando es nula, será tratada como basura.

4. El punto está aquí. De repente, nuestro ThreadLocal es nulo, lo que significa que el recolector de basura lo reciclará. Pero en este momento, nuestro ThreadLocalMap tiene el mismo ciclo de vida que Thread. No se reciclará. En este momento, hay es un fenómeno. Es decir, la clave de ThreadLocalMap se ha ido, pero el valor sigue ahí, lo que provoca una pérdida de memoria.

Solución: después de utilizar ThreadLocal, realice la operación de eliminación para evitar el desbordamiento de la memoria.

Supongo que te gusta

Origin blog.csdn.net/woaichihanbao/article/details/107907791
Recomendado
Clasificación