ThreadLocal浅析

1.目的

ThreadLocal目的是保存一些线程级别的全局变量,比如connection,或者事务上下文,避免这些值需要一直通过函数参数的方式一路传递。

2. 常见用法


3.实现分析

要实现上面这样的功能,最简单的想法是用一个Map<Thread,T>,如下:

这样也能实现ThreadLocal的效果,但是有一个问题,当对应的线程消失后,map中对应的线程值并不会被回收,从而造成内存泄露。

 

事实上ThreadLocal是这样做的:


注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;

 

下面看一下getMap(Thread t)方法:


在Thread类中:

由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象也会被回收。

这里为什么是一个ThreadLocalMap呢,因为一个线程可以有多个ThreadLocal变量,通过map.getEntry(this)取得对应的某个具体的变量。


最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:


这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,不能因为ThreadLocalMap的引用导致他不能被回收。注意,gc后entry的key.get()结果为null,但是回收实在后续插入元素时触发的。

 

 

附:

这里补充一下weakReference的用法供参考(当强引用不存在时,下次垃圾回收会回收弱引用所引用的对象):

结果输出:

 

4. FAQ

 

4.1 为什么一般的ThreadLocal用法都要加static,如下:

class Test {
    private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
}

 

answer:事实上,不一定是要static,但使用它的对象在业务需要范围类一定要是单例。因为根据前面的分析,ThreadLocalMap是以ThreadLocal对象为key的,如果Test类不是static,也不是单例的,那么两个Test对象就有两个key,取出来的数据肯定不同
class TestThreadLocal{    
    public static void main(String[] args) {
		Test t1 = new Test();
		Test t2 = new Test();
 
		t1.pool.set("a");
		System.out.println(t1.pool.get());
		System.out.println(t2.pool.get());
	}
}
class Test{
	public ThreadLocal pool = new ThreadLocal();
}
 

输出将会是:a,null

原因就无需多解释了。唯一需要啰嗦的一点是,就算一般情况都是单例,上面那个weakreference还是必要的,因为作为框架代码,不能保证正常使用的情况下一个线程有很多ThreadLocal,如果不用weakreference,就会有内存泄漏的风险,特别是针对线程池中的线程。

 

参考:

http://www.2cto.com/kf/201112/113451.html

你可能感兴趣的:(threadLocal)