26: Thread Local Variables - ThreadLocal

This article has participated in the "Newcomer Creation Ceremony" event to start the road of gold creation together.

ThreadLocal is a class introduced by JDK1.2 to provide local variables within a thread. Unlike ordinary variables, thread-local variables are bound to threads, and each thread has its own independent variable container. Thread-local variables work during the life cycle of a thread and are used to reduce the complexity of transferring common variables between multiple functions or components within a thread.

Example of use

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());
    }
}
复制代码

operation result:

R1 TEST STRING
R2 TEST STRING
复制代码

Seeing this, you may say, I can use Map to achieve this, why use ThreadLocal? First of all, ThreadLocal is bound to a thread. Each thread is independent of each other and does not affect each other. You can obtain thread local variables bound to the current thread through ThreadLocal anywhere. Of course, this is also achieved by using the Map of the thread ID as the Key. Secondly, the most important thing is that the thread local variables stored in ThreadLocal will automatically enter the recyclable state with the destruction of the thread, and the use of Map requires the map clearing operation to be displayed before the thread dies, otherwise the variable will always have a strong connection. Unable to enter recycling state.

Although the functions implemented by ThreadLocal can be implemented in other ways, they are not as simple as ThreadLocal. The meaning of the existence of tool classes is here. It integrates complex functions and provides a simple way to use them, making programming easier and code easier. clear.

Implementation Analysis

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;
}
复制代码

Through the above set method, you can see that the actual implementation of Thread is implemented through Map, and the attribute threadLocals is maintained internally in Thread:

ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码

The container of ThreadLocalMap is Thread$ThreadLocalMap$Entry[], and the reference relationship is shown in the figure:

And for 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;
	}
}
复制代码

It can be seen that Entry inherits WeakReference, which means that Entry's reference to ThreadLocal is a weak reference , which means that when the strong ThreadLocal reference we hold disappears, the ThreadLocal instance can enter the recyclable state. But the Entry instance will not follow into the recycling state.

对于普通线程因为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)
欢迎关注…

Guess you like

Origin juejin.im/post/7079762570864754695