Análisis de código fuente ThreadLocal y expansión InheritableThreadLocal

Hilo Local

1 Introducción a ThreadLocal

En muchos escenarios, queremos pasar parámetros que deben definirse mediante parámetros explícitos, pero cuando la pila de métodos es más profunda, es particularmente poco elegante, como pasar información del usuario al método llamado:

//controller传递用户信息到serviceA
controller.serviceA(user);

//继续向serviceB传递
serviceA.serviceB(user);

//最终传递到dao层
serviceB.dao(user);
复制代码

Cada método necesita definir un parámetro de usuario explícito, que está muy hinchado. ¿Hay alguna otra manera?

Alguien puede pensar en definir un atributo público o una variable estática, pero esto causará un problema inseguro en el hilo variable compartido de varios subprocesos, por lo que este atributo público debe estar bloqueado.

Una vez bloqueado, la eficiencia no es un poco lenta, ¿hay una manera más eficiente? En este momento, se utilizará *** ThreadLocal ***.

Las variables de sincronización mencionadas anteriormente adoptan un gobierno unificado, mientras que la estrategia adoptada por ThreadLocal es dividir y conquistar.

En términos oficiales: la clase ThreadLocal se usa para proporcionar variables locales dentro del hilo . Se puede garantizar que tales variables sean relativamente independientes de las variables en otros subprocesos cuando se accede a ellas en un entorno de subprocesos múltiples (se accede a través de métodos get o set) . Las instancias de ThreadLocal son generalmente private statictipos, utilizados para asociar hilos y contextos de hilo.

En pocas palabras:

  • ThreadLocal proporciona una copia de la variable dentro del hilo. Esta variable solo se comparte dentro de un solo hilo, y puede acceder fácilmente a la copia de la variable ThreadLocal dentro del hilo.

  • Las copias variables de TreadLocal entre varios subprocesos no se afectan entre sí.

  • ThreadLocal solo sobrevive en el ciclo de vida del hilo y muere a medida que el hilo muere (también puede llamar manualmente al método remove para eliminar la variable ThreadLocal).

De esta manera, la complejidad de pasar parámetros en diferentes métodos en un solo subproceso se resuelve con elegancia, ya que es una variable compartida dentro de cada subproceso, y no hay ningún problema de inseguridad de subprocesos múltiples.

Cabe señalar que:

  • ThreadLocal no es un mecanismo de sincronización y no resuelve el problema de seguridad de subprocesos en subprocesos múltiples.

  • ThreadLocal proporciona a cada hilo una copia independiente de la variable, aislando así los conflictos de acceso concurrente de datos por múltiples hilos. Debido a que cada subproceso tiene su propia copia de la variable, no hay problema de subprocesamiento múltiple inseguro.

El problema de pasar la información del usuario mencionada anteriormente se puede lograr a través de ThreadLocal:

public final class UserUtil {
	private static ThreadLocal<User> userThreadLocal = new ThreadLocal();
  
 	public static User getUser() {
	    return userThreadLocal.get();
	}
 	public static User setUser(User user) {
	    return userThreadLocal.set(user);
	}
	public static void removeUser() {
			userThreadLocal.remove();
	} 
}
复制代码
//设置User
controller(){
	UserUtil.setUser(user);
}

//获取User
serviceA(){
	UserUtil.getUser();
}
serviceB(){
	UserUtil.getUser();
}
dao(){
  UserUtil.getUser();
}
复制代码

2 El principio de realización de ThreadLocal

ThreadLocal crea una variable de copia independiente para cada subproceso. Entonces, ¿cómo funciona?

Desde el código fuente de ThreadLocal, puede obtener la siguiente información:

Obtenga las variables correspondientes a ThreadLocal llamando al método *** get ***. El código fuente del método *** get *** es el siguiente:

public T get() {
  	//获取当前线程
    Thread t = Thread.currentThread();
  	//根据线程获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
      	//获取Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
  	//设置并返回初始值
    return setInitialValue();
}
复制代码

Este método primero obtiene el hilo actual, y luego obtiene un *** ThreadLocalMap *** a través del hilo actual. Si ThreadLocalMap está vacío, establezca un valor inicial y regrese. Si el mapa obtenido no está vacío, el valor correspondiente se obtiene de acuerdo con el *** ThreadLocal *** actual y se devuelve.

De acuerdo con este método podemos obtener la siguiente información:

  • Los valores correspondientes de ThreadLocal se almacenan en *** ThreadLocalMap ***.

  • ThreadLocalMap se obtiene en base a la instancia de instancia *** de hilo actual ***.

  • Si *** ThreadLocalMap *** es NULL o ThreadLocal no tiene un valor correspondiente, devuelva el valor inicial (método setInitialValue).

Ahora que sabemos que el valor de ThreadLocal existe en *** ThreadLocalMap ***, continuamos analizando ThreadLocalMap map = getMap(t);la implementación específica de este código.

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
复制代码

Este método devuelve directamente, un atributo *** threadLocals *** en el hilo, un seguimiento adicional puede encontrar que este atributo se define en la clase *** Thread ***.

public class Thread implements Runnable {
	...
	
	ThreadLocal.ThreadLocalMap threadLocals = null;
	
	...
}
复制代码

A través del código fuente, ya sabemos muy claramente que *** ThreadLocalMap se guarda en Thread *** , y *** ThreadLocal *** los pares clave-valor correspondientes se guardan en *** ThreadLocalMap ***. El diagrama esquemático es el siguiente:

3 Análisis de código fuente parcial de ThreadLocal

En la última sección, hemos analizado el código fuente del método *** get ***, y luego analizaremos los otros códigos fuente principales.

método establecido:

public void set(T value) {
  	//获取当前线程
    Thread t = Thread.currentThread();
  	//获取ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
复制代码

La función del método set *** es usar la instancia actual *** ThreadLocal *** como la clave, y guardar el valor correspondiente como un par clave-valor en *** ThreadLocalMap ***. Si no se ha creado *** ThreadLocalMap ***, cree un nuevo *** ThreadLocalMap .

Cree *** ThreadLocalMap ***, el createMap(t, value)método de implementación específico es el siguiente.

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码

El método *** createMap *** directamente *** nuevo *** tiene un objeto *** ThreadLocalMap ***, los parámetros pasados ​​son la instancia actual de *** ThreadLocal *** y un valor variable que debe guardarse, Trace en el método de construcción *** ThreadLocalMap ***.

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
  	//创建Entry数组
    table = new Entry[INITIAL_CAPACITY];
  	//计算元素位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
  	//创建Entry
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
复制代码

El método de construcción *** ThreadLocalMap *** utiliza la longitud inicial *** INITIAL_CAPACITY *** para crear una matriz *** Entry *** y calcular el índice del elemento inicial.

Después de guardar el *** par clave-valor ThreadLocal *** en la posición correspondiente en la matriz, establezca el tamaño en 1 e inicialice el límite inferior de expansión de capacidad.

Se puede ver a partir de este código que todos los pares de clave-valor *** ThrealLocal *** la ubicación de almacenamiento final es una matriz de entrada ***, y la clase de entrada se define en la clase *** ThreadLocalMap ***.

public class ThreadLocal<T> {    
  ...
    static class ThreadLocalMap {
      ...
        //弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
      ...
    }
  ...
}
复制代码

Dado que Entry hereda *** WeakReference ***, ThreadLocal se define como un objeto de referencia débil, por lo que siempre que no haya una referencia fuerte a ThreadLocal, ThreadLocal se reciclará independientemente de si la memoria es suficiente al activar GC.

Pero hay un problema en este momento: si ThreadLocal se recicla y el valor en la *** Entrada *** correspondiente no se recicla, entonces este valor nunca será accesible. A medida que se reciclan más y más ThreadLocals, no se pueden recuperar más y más valores, lo que eventualmente causará pérdidas de memoria.

Algunas personas dirían, ¿es suficiente diseñar referencias fuertes? De hecho, diseñarlo como una referencia fuerte no tiene ningún efecto. Si no establece manualmente la referencia como nula, causará que la clave (ThreadLocal) y el valor no se reciclen, y eventualmente provocará una pérdida de memoria.

Diseñado como una referencia fuerte también hará que el valor se establezca manualmente en nulo, y la clave (ThreadLocal) no se haya reciclado, también provocará una pérdida de memoria.

Es por eso que ThreadLocal está diseñado como un objeto de referencia débil. Cuando el valor se recupera manualmente, ThreadLocal también se recicla, lo que sin duda es un seguro.

El análisis anterior también se menciona a menudo que después de usar ThreadLocal, debe llamar manualmente *** set (), get (), remove () *** de lo contrario se producirá una pérdida de memoria.

Podemos ver cómo *** remove () *** maneja este problema.

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) {
          	//清除key
            e.clear();
          	//进一步清除
            expungeStaleEntry(i);
            return;
        }
    }
}
复制代码

*** remove () *** recorre la mesa y llama *** e.clear () *** para borrar la clave.

Y el siguiente método *** expungeStaleEntry *** hace algunas cosas:

  • Borre el elemento i-ésimo en *** valor *** y *** tabla *** juntos.
  • Desplácese por los elementos de la tabla para eliminar los elementos con clave igual a nulo
  • Reorganizar los elementos de la mesa.
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
  	//设置value和tab[staleSlot]为null
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    Entry e;
    int i;
  	//循环所有元素,并清除key==null的元素
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
      	//清除key==null的元素
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
          	//重新排列元素位置
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}
复制代码

Del mismo modo, en el conjunto ThreadLocal *** set (), el método get () *** finalmente llamará al método *** expungeStaleEntry *** para borrar el valor de la clave es nulo, no los repetirá aquí.

4 Extensión de ThreadLocal

ThreadLocal se utiliza para resolver el intercambio de recursos dentro del hilo, y para lograr el *** aislamiento de recursos entre hilos ***, pero en algunos escenarios necesitamos acceder a los recursos del hilo principal en el hilo secundario, ¿se puede lograr esto? Por supuesto, esta vez necesitas usar otra clase: InheritabIeThreadLocal .

Es fácil notar en el código fuente de Thread que hay otro atributo *** heredableThreadLocals *** bajo el atributo *** threadLocals ***.

public class Thread implements Runnable {
	...
	
	ThreadLocal.ThreadLocalMap threadLocals = null;
  
  //继承map
	ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
	...
}
复制代码

Los tipos de estas dos propiedades son *** ThreadLocal.ThreadLocalMap ***, y *** *** heredableThreadLocals *** hace referencia a la clase *** InheritableThreadLocal ***.

Echemos un vistazo al código fuente de la clase *** InheritableThreadLocal ***

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}
复制代码

InheritableThreadLocal *** hereda *** ThreadLocal y cubre los tres métodos de *** childValue, getMap y createMap ***.

getMap, createMap *** cambió de la operación original *** t.threadLocals *** a la operación *** t.inheritableThreadLocals , y el método *** childValue *** creará herencia en la clase *** ThreadLocalMap *** Usado en el mapa.

public class ThreadLocal<T> {
  	...
    //创建一个继承map
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }
		...
    //根据parentMap创建一个新的ThreadLocalMap,其中的元素key和value值相同
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];
      	//循环创建新的Entry
        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
              	//过滤key等于null的值
                if (key != null) {
                  	//childValue返回的值即是e.value
                    Object value = key.childValue(e.value);
                  	//key,value值保持和父线程一致
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }
  ...
}
复制代码

createInheritedMap *** copia un nuevo *** ThreadLocalMap basado en el *** parentMap *** entrante para filtrar el valor de clave igual a nulo, y otros elementos clave y valores de valor son consistentes con el hilo primario.

¿Cuándo creó el hilo el mapa de herencia? Cuando se inicializa el subproceso, se llama al método *** init *** de la clase *** Thread ***, y cuando especificamos *** heredarThreadLocals *** como *** verdadero *** y el subproceso principal ** * inheritableThreadLocals *** Cuando no es igual a nulo, el hilo creará un mapa heredado.

public class Thread implements Runnable {
  	....
    //绝大部分情况下调用本初始化方法
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
      	//inheritThreadLocals 默认为 true
        init(g, target, name, stackSize, null, true);
    }
  
    //线程初始化
		private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
				...
          //父类线程为创建本线程的运行线程
          Thread parent = currentThread();
      	...
          //创建继承map
       		if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            		this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
      	...
    }
		...
}
复制代码

Aquí hay tres puntos que necesitan su atención:

  • En la mayoría de los casos *** heredaThreadLocals *** por defecto es verdadero.

  • El Thread parent = currentThread()subproceso principal crea un nuevo subproceso para el subproceso en ejecución , es decir, el subproceso actual en ejecución crea un nuevo subproceso. En este momento, el subproceso en ejecución todavía se está ejecutando y el nuevo subproceso no se ha inicializado, por lo tanto parent = currentThread().

  • El *** del subproceso secundario heredableThreadLocals *** se copia de acuerdo con el *** del subproceso principal heredableThreadLocals *** del subproceso principal, lo que equivale a pasar el valor del subproceso *** *** heredableThreadLocals del subproceso principal, de modo que el subproceso secundario se realice Obtenga el valor del hilo primario.

Comparemos el uso real de *** ThreadLocal *** y *** InheritableThreadLocal *** por un ejemplo.

package com.gavin.test;

public class ThreadLocalTest {

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

    public static void main(String[] args) {
      	//设置主线程的值
        ThreadLocalTest.threadLocal.set("threadLocal-main");
      	//启用线程1
        new Thread(() -> {
          	//设置线程1的值
            ThreadLocalTest.threadLocal.set("threadLocal-1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
        }).start();

      	//启用线程2
        new Thread(() -> {
          	//设置线程2的值
            ThreadLocalTest.threadLocal.set("threadLocal-2");
            System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
        }).start();

        System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
    }
}

复制代码

Este código define un atributo *** threadLocal ***, y este atributo se establece en los tres hilos. El resultado es el siguiente:

主线程==threadLocal-main
线程2==threadLocal-2
线程1==threadLocal-1
复制代码

Se puede concluir que los atributos threadLocal de los tres hilos no interfieren entre sí. Cambiemos el código y veamos si el hilo secundario puede obtener el valor del hilo principal.

package com.gavin.test;

public class ThreadLocalTest {

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

    public static void main(String[] args) {
      
        ThreadLocalTest.threadLocal.set("threadLocal-main");
        new Thread(() -> {
          	//直接打印值
            System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
        }).start();

        new Thread(() -> {
          	//直接打印值
            System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
        }).start();

        System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
    }
}

复制代码

Los resultados son los siguientes:

线程1==null
主线程==threadLocal-main
线程2==null
复制代码

A partir de los resultados, se puede concluir que los subprocesos no pueden obtener el valor threadLocal del hilo principal, y una vez más demuestra que el threadLocal entre los hilos está aislado entre sí.

Para lograr el acceso del subproceso secundario al valor del subproceso principal, intentamos usar *** heredableThreadLocal *** para lograrlo.

package com.gavin.test;

public class InheritableThreadLocalTest {

    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
        new Thread(() -> {
            System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        new Thread(() -> {
            System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
    }
}

复制代码

Los resultados son los siguientes:

线程1==inheritableThreadLocal-main
主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-main
复制代码

De los resultados se puede concluir que el subproceso obtiene el valor del subproceso principal. Si se cambia el valor del subproceso, ¿afectará al subproceso principal u otros subprocesos?

De acuerdo con el análisis del código fuente anterior, el subproceso principal pasará su propio ThreadLocal *** heredable al subproceso secundario. El subproceso secundario vuelve a ingresar el nuevo objeto Entrada para guardar la clave y el valor, por lo que la modificación del subproceso secundario no afectará el valor del subproceso principal. No afectará a otros subprocesos y solo se pasará a sus propios subprocesos. Verifíquelo.

package com.gavin.test;

public class InheritableThreadLocalTest {

    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
        new Thread(() -> {
            InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());

            new Thread(() -> {
                System.out.println("线程1的子线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
            }).start();

        }).start();

        new Thread(() -> {
            InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-2");
            System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
        }).start();

        System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
    }
}

复制代码

Los resultados son los siguientes

主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-2
线程1==inheritableThreadLocal-1
线程1的子线程==inheritableThreadLocal-1
复制代码

Como supusimos:

  • El subproceso secundario puede obtener el valor del subproceso principal a través de *** InheritableThreadLocal *** (de hecho, es un nuevo objeto de Entrada).
  • La modificación del subproceso secundario *** El valor InheritableThreadLocal *** no afectará el valor del subproceso principal y otros subprocesos.
  • El hilo secundario *** InheritableThreadLocal *** se pasará a su hilo secundario.

Columna de pepitas: juejin.im/user/5ba21d ...

Autor: GavinKing

La originalidad no es fácil, obtenga el consentimiento del autor para la reimpresión y traiga información de copyright, gracias

Supongo que te gusta

Origin juejin.im/post/5e9d5a4e6fb9a03c8027b142
Recomendado
Clasificación