Este artículo le permite comprender completamente ThreadLocal

Este artículo se comparte desde Huawei Cloud Community " [Alta concurrencia] Este artículo lo llevará a comprender ThreadLocal a fondo ", autor: Binghe.

Todos sabemos que acceder a la misma variable compartida en un entorno de subprocesos múltiples puede causar problemas de seguridad de subprocesos. Para garantizar la seguridad de los subprocesos, a menudo bloqueamos el acceso a esta variable compartida para lograr el efecto de sincronización, como se muestra en la siguiente figura Mostrar .

cke_150.png

Aunque el bloqueo de variables compartidas puede garantizar la seguridad de los subprocesos, aumenta las habilidades de los desarrolladores en el uso de bloqueos.Si los bloqueos no se usan correctamente, se producirán problemas de interbloqueo. Y ThreadLocal puede lograr que después de crear la variable, cada subproceso acceda a la variable local del propio subproceso al acceder a la variable .

¿Qué es ThreadLocal?

JDK proporciona ThreadLocal y admite variables locales de subprocesos. Es decir, si creamos una variable ThreadLocal, cada hilo que acceda a la variable tendrá una copia local de la variable. Si varios subprocesos leen y escriben esta variable al mismo tiempo, en realidad operan en la variable en la propia memoria local del subproceso, evitando así el problema de la seguridad del subproceso.

cke_151.png

Ejemplo de uso de ThreadLocal

Por ejemplo, usamos ThreadLocal para guardar e imprimir información variable relacionada, el programa es el siguiente.

public class ThreadLocalTest { 

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

public static void main(String[] args){ 

//Crear el primer hilo 

Thread threadA = new Thread(()->{ 

threadLocal . set("ThreadA: " + Thread.currentThread().getName()); 

System.out.println("El valor en la variable local del hilo A: " + threadLocal.get()); 

}); 

//crear El segundo hilo 

Thread threadB = new Thread(()->{ 

threadLocal.set("ThreadB: " + Thread.currentThread().getName()); 

System.out.println("El valor en la variable local del hilo B es : " + threadLocal.get()); 

}); 

//Iniciar el hilo A y el hilo B 

threadA.start(); 

threadB.start(); 

} 

}

Ejecute el programa y la información del resultado impreso es la siguiente.

El valor de la variable local del subproceso A es: SubprocesoA:Subproceso-0 

El valor de la variable local del subproceso B es: SubprocesoB:Subproceso-1

En este punto, agregamos la operación de eliminación de variables en ThreadLocal al subproceso A, como se muestra a continuación.

public class ThreadLocalTest { 

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

public static void main(String[] args){ 

//Crear el primer hilo 

Thread threadA = new Thread(()->{ 

threadLocal . set("ThreadA: " + Thread.currentThread().getName()); 

System.out.println("El valor en la variable local del hilo A: " + threadLocal.get()); 

threadLocal.remove(); 

System.out.println("Después de que el subproceso A elimine la variable local, el valor en ThreadLocal es: " + subprocesoLocal.get()); } 

); 

//Crear un segundo subproceso 

Subproceso subprocesoB = nuevo Subproceso(()->{ 

threadLocal .set("ThreadB: " + Thread.currentThread().getName()); 

System.out.println("El valor en la variable local del subproceso B: " + threadLocal.get()); 

System.out.println("El subproceso B no eliminó la variable local: " + threadLocal.get()); 

});

//Iniciar el subproceso A y el subproceso B 

threadA.start(); 

threadB.start(); 

} 

}

El resultado de la operación en este momento es el siguiente.

El valor en la variable local del subproceso A: SubprocesoA: Subproceso-0 

El valor en la variable local del subproceso B: SubprocesoB: Subproceso-1 

El subproceso B no eliminó la variable local: SubprocesoB: Subproceso-1 

El valor en ThreadLocal después del subproceso A eliminó la variable local para: nulo

A través del programa anterior, podemos ver que las variables almacenadas en ThreadLocal por el subproceso A y el subproceso B no interfieren entre sí, las variables almacenadas por el subproceso A solo pueden ser accedidas por el subproceso A, y las variables almacenadas por el subproceso B solo pueden ser accedido por el hilo B.

Principio de ThreadLocal

Primero, veamos el código fuente de la clase Thread, como se muestra a continuación.

subproceso de clase pública implementa Runnable { 

/********** Omitir N líneas de código **************/ 

ThreadLocal.ThreadLocalMap threadLocals = nulo 

; ; 

/***********Omitir N líneas de código**************/ 

}

Se puede ver en el código fuente de la clase Thread que hay variables miembro threadLocals y heritageThreadLocals en la clase ThreadLocal.Estas dos variables miembro son variables de tipo ThreadLocalMap, y los valores iniciales de ambas son nulos. La variable se instancia solo cuando el subproceso actual llama al método set() o al método get() de ThreadLocal por primera vez.

Cabe señalar aquí que las variables locales de cada subproceso no se almacenan en la instancia de ThreadLocal, sino que se almacenan en la variable threadLocals del subproceso que llama. Es decir, la variable local almacenada al llamar al método set() de ThreadLocal se almacena en el espacio de memoria del subproceso específico, y la clase ThreadLocal solo proporciona los métodos set() y get() para almacenar y leer el valor. de la variable local Cuando se llama al método set() de la clase ThreadLocal, el valor a almacenar se almacena en los threadLocals del hilo que llama, y ​​cuando se llama al método get() de la clase ThreadLocal, el valor almacenado se extrae de la variable threadLocals del hilo actual.

A continuación, analicemos la lógica de implementación de los métodos set(), get() y remove() de la clase ThreadLocal.

método set()

El código fuente del método set() se muestra a continuación.

public void set(T value) { 

//Obtener el subproceso actual 

Thread t = Thread.currentThread(); 

//Usar el subproceso actual como clave para obtener el objeto ThreadLocalMap 

ThreadLocalMap map = getMap(t); 

//El ThreadLocalMap obtenido el objeto no está vacío 

si (mapa! = nulo) 

// Establezca el valor de value 

map.set (este, valor); 

más 

// El objeto ThreadLocalMap obtenido está vacío, cree la variable threadLocals en la clase Thread 

createMap (t, valor) ); 

}

En el método set(), primero obtenga el subproceso que llama al método set() y luego use el subproceso actual como clave para llamar al método getMap(t) para obtener el objeto ThreadLocalMap. t) el método es el siguiente.

ThreadLocalMap getMap(Thread t) { 

return t.threadLocals; 

}

Se puede ver que el método getMap(Thread t) obtiene la variable miembro threadLocals de la propia variable thread.

En el método set(), si el objeto devuelto al llamar al método getMap(t) no está vacío, el valor se establece en la variable miembro threadLocals de la clase Thread, y la clave pasada es el objeto this del ThreadLocal actual, y el valor se pasa a través del método set () para pasar el valor.

Si el objeto devuelto al llamar al método getMap(t) está vacío, el programa llama al método createMap(t, value) para instanciar la variable miembro threadLocals de la clase Thread.

void createMap(Thread t, T firstValue) { 

t.threadLocals = new ThreadLocalMap(this, firstValue); 

}

Eso es para crear la variable threadLocals del hilo actual.

obtener () método

El código fuente del método get() se muestra a continuación.

public T get() { 

//Obtenga el subproceso actual 

Thread t = Thread.currentThread(); 

//Obtenga la variable miembro threadLocals del 

subproceso actual ThreadLocalMap map = getMap(t); 

//La variable threadLocals obtenida no está vacía 

si (map ! = null) { 

//Devuelve el valor correspondiente a la variable local 

ThreadLocalMap.Entry e = map.getEntry(this); 

if (e != null) { 

@SuppressWarnings("unchecked") 

T result = (T) e.value; 

return result; 

} 

} 

//Inicializar el valor de la variable miembro threadLocals 

return setInitialValue(); 

}

Obtenga la variable miembro threadLocals a través del subproceso actual, si la variable miembro threadLocals no está vacía, devuelva directamente la variable local vinculada al subproceso actual; de lo contrario, llame al método setInitialValue() para inicializar el valor de la variable miembro threadLocals.

private T setInitialValue() { 

//Llamar al método para inicializar el valor 

T value = initialValue(); 

Thread t = Thread.currentThread(); 

//Obtener la variable miembro threadLocals de acuerdo con el thread actual 

ThreadLocalMap map = getMap(t); 

if (map ! = null) 

//threadLocals no está vacío, establece el valor 

map.set(this, value); 

else 

//threadLocals está vacío, crea la variable threadLocals 

createMap(t, value); 

return value; 

}

Entre ellos, el código fuente del método initialValue() es el siguiente.

protegido T initialValue() { 

return null; 

}

Como se puede ver en el código fuente del método initialValue(), este método puede ser anulado por subclases.En la clase ThreadLocal, este método devuelve nulo directamente.

método remove()

El código fuente del método remove() se muestra a continuación.

public void remove() { 

//Según el subproceso actual para obtener la variable miembro threadLocals 

ThreadLocalMap m = getMap(Thread.currentThread()); 

if (m != null) 

//La variable miembro threadLocals no está vacía, luego elimínela el valor 

m.remove(esto); 

}

La implementación del método remove() es relativamente simple. Primero, obtenga la variable miembro threadLocals de acuerdo con el hilo actual. Si no está vacío, elimine el valor de value directamente.

Nota: si el subproceso que llama no termina de manera consistente, la variable local siempre se almacenará en la variable miembro threadLocals del subproceso que llama. Por lo tanto, si no necesita usar la variable local, puede llamar al método remove() de ThreadLocal para eliminar la variable local del subproceso actual. Eliminado de la variable miembro threadLocals para evitar problemas de desbordamiento de memoria.

Las variables ThreadLocal no son transitivas

El uso de ThreadLocal para almacenar variables locales no es transitivo, es decir, después de que el mismo ThreadLocal establece un valor en el hilo padre, el valor no se puede obtener en el hilo hijo, este fenómeno muestra que las variables locales almacenadas en ThreadLocal no son transitivas . .

A continuación, veamos un fragmento de código, como se muestra a continuación.

public class ThreadLocalTest { 

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

public static void main(String[] args){ 

//Establecer el valor en el subproceso principal 

threadLocal.set("ThreadLocalTest"); 

/ / Obtener el valor en el subproceso secundario 

Thread thread = new Thread(new Runnable() { 

@Override 

public void run() { 

System.out.println("El subproceso secundario obtiene el valor: " + threadLocal.get()); 

} 

}); 

//Inicie el subproceso secundario 

thread.start(); 

//Obtenga el valor en el subproceso principal 

System.out.println("El subproceso principal obtiene el valor: " + threadLocal.get()); 

} 

}

La salida de información de resultados al ejecutar este código es la siguiente.

El subproceso principal obtiene el valor: ThreadLocalTest 

El subproceso secundario obtiene el valor: nulo

A través del programa anterior, podemos ver que después de establecer un valor para ThreadLocal en el subproceso principal, este valor no se puede obtener en el subproceso secundario. ¿Hay alguna manera de obtener el valor establecido por el hilo principal en el hilo secundario? En este punto, podemos usar InheritableThreadLocal para resolver este problema.

Ejemplo de uso de InheritableThreadLocal

La clase InheritableThreadLocal se hereda de la clase ThreadLocal, que permite que el subproceso secundario acceda al valor de la variable local establecida en el subproceso principal. Por ejemplo, reescribimos la variable estática threadLocal en la clase ThreadLocalTest como una instancia de la clase InheritableThreadLocal, como mostrado a continuación.

public class ThreadLocalTest { 

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

public static void main(String[] args){ 

//Establecer el valor en el subproceso principal 

threadLocal.set("ThreadLocalTest"); 

/ / Obtener el valor en el subproceso secundario 

Thread thread = new Thread(new Runnable() { 

@Override 

public void run() { 

System.out.println("El subproceso secundario obtiene el valor: " + threadLocal.get()); 

} 

}); 

//Inicie el subproceso secundario 

thread.start(); 

//Obtenga el valor en el subproceso principal 

System.out.println("El subproceso principal obtiene el valor: " + threadLocal.get()); 

} 

}

En este punto, la salida de información de resultados al ejecutar el programa es la siguiente.

El hilo principal obtiene el valor: ThreadLocalTest 

El hilo secundario obtiene el valor: ThreadLocalTest

Se puede ver que cuando se usa la clase InheritableThreadLocal para almacenar variables locales, el subproceso secundario puede obtener las variables locales establecidas en el subproceso principal.

Principio de InheritableThreadLocal

Primero, veamos el código fuente de la clase InheritableThreadLocal, como se muestra a continuación.

public class InheritableThreadLocal<T> extiende 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); 

} 

}

Según el código fuente de la clase InheritableThreadLocal, la clase InheritableThreadLocal hereda de la clase ThreadLocal y reescribe el método childValue(), el método getMap() y el método createMap() de la clase ThreadLocal. Es decir, cuando se llama al método set() de ThreadLocal, se crea la variable miembro de threadLocals heredable del subproceso Thread actual en lugar de la variable miembro threadLocals.

Aquí, debemos pensar en una pregunta: ¿Cuándo se llama al método childValue() de la clase InheritableThreadLocal?  Esto requiere que observemos el método de construcción de la clase Thread, como se muestra a continuación.

public Thread() { 

init(null, null, "Thread-" + nextThreadNum(), 0); 

} 

subproceso público (objetivo ejecutable) { 

init (null, destino, "Subproceso-" + nextThreadNum(), 0); 

} 

Thread(Destino ejecutable, AccessControlContext acc) { 

init(null, target, "Thread-" + nextThreadNum(), 0, acc, false); 

} 

hilo público (grupo ThreadGroup, objetivo ejecutable) { 

init (grupo, objetivo, "Thread-" + nextThreadNum(), 0); 

} 

Public Thread(String name) { 

init(null, null, name, 0); 

} 

public Thread(ThreadGroup group, String name) { 

init(group, null, name, 0); 

} 

subproceso público (objetivo ejecutable, nombre de cadena) { 

init (nulo, destino, nombre,

} 

public Thread(ThreadGroup group, Runnable target, String name) { 

init(group, target, name, 0); 

} 

subproceso público (grupo de grupo de subprocesos, objetivo ejecutable, nombre de cadena, 

tamaño de pila largo) { 

init (grupo, objetivo, nombre, tamaño de pila); 

}

Se puede ver que el constructor de la clase Thread finalmente llama al método init(), así que veamos el método init(), como se muestra a continuación.

private void init(ThreadGroup g, Runnable target, String name, 

long stackSize, AccessControlContext acc, 

boolean heritageThreadLocals) { 

/************省略部分源码*********** */ 

if (inheritThreadLocals && parent.inheritableThreadLocals != null) 

this.inheritableThreadLocals = 

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); 

/* Guarda el tamaño de pila especificado en caso de que a la VM le importe */ 

this.stackSize = stackSize; 

/* Establecer ID de subproceso */ 

tid = nextThreadID(); 

}

Se puede ver que en el método init(), se evaluará si la variable heritageThreadLocals pasada es verdadera, y si los elementos locales del subproceso heredado en el subproceso principal son nulos, si la variable heritageThreadLocals pasada es verdadera y, al mismo tiempo, la variable heritageThreadLocals en el subproceso principal no es nulo, luego llame al método createInheritedMap() de la clase ThreadLocal.

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { 

return new ThreadLocalMap(parentMap); 

}

En createInheritedMap(), se crea un nuevo objeto ThreadLocalMap utilizando la variable legacyThreadLocals del subproceso principal como parámetro. Luego, en el método init() de la clase Thread, este objeto ThreadLocalMap se asignará a la variable de miembro heredableThreadLocals del subproceso secundario.

A continuación, echemos un vistazo a lo que hace el constructor ThreadLocalMap, como se muestra a continuación.

privado ThreadLocalMap(ThreadLocalMap parentMap) { 

Entry[] parentTable = parentMap.table; 

int len ​​= parentTable.longitud; 

establecer umbral (largo); 

tabla = nueva Entrada[largo]; 

for (int j = 0; j < len; j++) { 

Entry e = parentTable[j]; 

if (e != null) { 

@SuppressWarnings("unchecked") 

ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); 

if (key != null) { 

//调用重写的childValue方法

Valor del objeto = key.childValue(e.value); 

Entrada c = nueva entrada (clave, valor); 

int h = clave.threadLocalHashCode & (len - 1); 

while (table[h] != null) 

h = nextIndex(h, len); 

mesa[h] = c; 

tamaño++; 

} 

} 
} 

}

En el constructor de ThreadLocalMap, se llama al método childValue() reescrito por la clase InheritableThreadLocal. La clase InheritableThreadLocal guarda las variables locales en la variable legacyThreadLocals del hilo Thread reescribiendo el método getMap() y el método createMap() Cuando el hilo establece la variable a través del método set() y el método get() del InheritableThreadLocal clase, creará la variable heredableThreadLocals actual del subproceso. En este momento, si el subproceso principal crea un subproceso secundario, el constructor de la clase Subproceso copiará la variable local en la variable heritageThreadLocals del subproceso principal y la guardará en la variable heritageThreadLocals del subproceso secundario.

Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud por primera vez~

Aclaración sobre MyBatis-Flex plagiando el navegador MyBatis-Plus Arc lanzado oficialmente 1.0, afirmando ser un reemplazo para Chrome OpenAI lanzó oficialmente la versión de Android ChatGPT VS Code optimizó la compresión de ofuscación de nombres, ¡reducción de JS incorporado en un 20%! LK-99: ¿El primer superconductor de temperatura y presión ambiente? Musk "compró por cero yuanes" y robó la cuenta de Twitter @x. El Comité Directivo de Python planea aceptar la propuesta PEP 703, haciendo que el bloqueo del intérprete global sea opcional . El número de visitas al software de código abierto y de captura de paquetes gratuito del sistema Stack Overflow ha disminuido significativamente, y Musk dijo que ha sido reemplazado por LLM
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4526289/blog/10092234
Recomendado
Clasificación