26: Variables locales de subprocesos - ThreadLocal

Este artículo ha participado en el evento "Ceremonia de creación de recién llegados" para comenzar juntos el camino de la creación de oro.

ThreadLocal es una clase introducida por JDK1.2 para proporcionar variables locales dentro de un hilo. A diferencia de las variables ordinarias, las variables locales de subprocesos están vinculadas a subprocesos, y cada subproceso tiene su propio contenedor de variables independiente. Las variables locales de subprocesos funcionan durante el ciclo de vida de un subproceso y se utilizan para reducir la complejidad de transferir variables comunes entre varias funciones o componentes dentro de un subproceso.

Ejemplo de uso

public class ThreadLocalDemo {

    static final ThreadLocal<String> TL_DEMO = new ThreadLocal<>();
    public static void main(String[] args) {
        Runnable rn1 = new Runnable() {
            @Override
            public void run() {
                setVal("R1 TEST STRING");
                printVal();
            }
        };
        Runnable rn2 = new Runnable() {
            @Override
            public void run() {
                setVal("R2 TEST STRING");
                printVal();
            }
        };
        new Thread(rn1).start();
        new Thread(rn2).start();
    }
    
    public static void setVal(String val) {
        TL_DEMO.set(val);
    }

    public static void printVal() {
        System.out.println(TL_DEMO.get());
    }
}
复制代码

resultado de la operación:

R1 TEST STRING
R2 TEST STRING
复制代码

Al ver esto, puede decir, puedo usar Map para lograr esto, ¿por qué usar ThreadLocal? En primer lugar, ThreadLocal está vinculado a un subproceso. Cada subproceso es independiente entre sí y no se afecta entre sí. Puede obtener variables locales de subproceso vinculadas al subproceso actual a través de ThreadLocal en cualquier lugar. Por supuesto, esto también se logra usando el Mapa de la identificación del subproceso como Clave. En segundo lugar, lo más importante es que las variables locales del subproceso almacenadas en ThreadLocal entrarán automáticamente en el estado reciclable con la destrucción del subproceso, y el uso de Map requiere que se muestre la operación de limpieza del mapa antes de que el subproceso muera, de lo contrario, la variable siempre tenga una conexión fuerte No se puede ingresar al estado de reciclaje.

Aunque las funciones implementadas por ThreadLocal se pueden implementar de otras maneras, no son tan simples como ThreadLocal. El significado de la existencia de clases de herramientas está aquí. Integra funciones complejas y proporciona una forma sencilla de usarlas, facilitando la programación y el código. más fácil claro

Análisis de Implementación

public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
	return t.threadLocals;
}
复制代码

A través del método establecido anterior, puede ver que la implementación real de Thread se implementa a través de Map, y el atributo threadLocals se mantiene internamente en Thread:

ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码

El contenedor de ThreadLocalMap es Thread$ThreadLocalMap$Entry[], y la relación de referencia se muestra en la figura:

Y para Thread$ThreadLocalMap$Entry:

private Entry[] table;
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 Entry hereda WeakReference, lo que significa que la referencia de Entry a ThreadLocal es una referencia débil , lo que significa que cuando desaparece la referencia fuerte de ThreadLocal que tenemos, la instancia de ThreadLocal puede entrar en el estado reciclable. Pero la instancia de Entrada no seguirá en el estado de reciclaje.

对于普通线程因为ThreadLocalMap维护在Thread内部,因此当Thread死亡时,其维护的线程本地变量容器ThreadLocalMap也会进入可回收状态。但是对于线程池线程,我们知道线程池运行过程中线程池的核心线程在未设置的情况下是不会销毁的,这意味着线程维护的ThreadLocalMap在线程池运行期间会一直存在,此时可以通过手动调用ThreadLocal.remove()来使Entry实例的强引用消失进入可回收状态。

但是也并没有一个地方说使用ThreadLocal需要强制调用ThreadLocal.remove()方法呀,这样一直不回收又一直有新对象进来的话为什么不会造成OOM呢?这是因为ThreadLocal会有一个自动的懒清理方式。ThreadLocalMap的存储方式不同于HashMap采用的拉链法,而是采用的开地址法

/**下标确认*/
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)])
/**下标确认*/

private static int nextIndex(int i, int len) {
	return ((i + 1 < len) ? i + 1 : 0);
}
复制代码

遭遇Hash冲突且发现存在Key已经被回收的无效Entry实例时,则会尝试性的进行一部分容器槽的扫描清除操作(不仅仅局限于冲突的那个,而是一定范围的扫描),使得Entry进入可回收状态。

那么为什么不将value也设置成弱引用呢?这样不是就可以不用手动执行ThreadLocal.remove(),达到像ThreadLocal实例一样的自动回收呢?这是因为ThreadLocal无法确定程序逻辑会在什么时间断开对value实例的强引用持有,如果将value也设置成弱引用,那么在程序逻辑失去强引用持有后,value就会在下一次GC时被回收,但是也许此时程序还持有ThreadLocal实例,并在接下来的运行中通过ThreadLocal实例去获取线程本地变量value,然后此时value却已被GC,造成意料之外的错误。

尽管如此,线程池模式下使用ThreadLocal依然建议在合适的地方调用ThreadLocal.remove()进行对象的及时回收,避免造成内存泄漏。

PS:
开发成长之旅 [持续更新中...]
上篇导航:25:线程间通信协作-Exchanger - 掘金 (juejin.cn)
下篇导航:27:JAVA中的各种锁 - 掘金 (juejin.cn)
欢迎关注…

Supongo que te gusta

Origin juejin.im/post/7079762570864754695
Recomendado
Clasificación