ThreadLocal是Java中的一种线程绑定机制,可以为每一个使用该变量的线程都提供一个变量值的副本,并且每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本发生冲突,解决了变量并发访问的冲突问题。
每个线程内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals,这个 threadLocals 存储了与该线程相关的所有ThreadLocal 变量及其对应的值,也就是说,ThreadLocal 变量及其对应的值就是该Map中的一个 Entry,更直白地,threadLocals中每个Entry的Key是ThreadLocal 变量本身,而Value是该ThreadLocal变量对应的值。
ThreadLocal可能引起的内存泄露
下面是ThreadLocalMap的部分源码,我们可以看出ThreadLocalMap里面对Key的引用是弱引用。那么,就存在这样的情况:当释放掉对threadlocal对象的强引用后,map里面的value没有被回收,但却永远不会被访问到了,因此ThreadLocal存在着内存泄露问题。
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
...
}
看下面的图示, 实线代表强引用,虚线代表弱引用。每个thread中都存在一个map,map的类型是上文提到的ThreadLocal.ThreadLocalMap,该map中的key为一个ThreadLocal实例。这个Map的确使用了弱引用,不过弱引用只是针对key,每个key都弱引用指向ThreadLocal对象。一旦把threadlocal实例置为null以后,那么将没有任何强引用指向ThreadLocal对象,因此ThreadLocal对象将会被 Java GC 回收。但是,与之关联的value却不能回收,因为存在一条从current thread连接过来的强引用。 只有当前thread结束以后, current thread就不会存在栈中,强引用断开,Current Thread、Map及value将全部被Java GC回收。
所以,得出一个结论就是:只要这个线程对象被Java GC回收,就不会出现内存泄露。但是如果只把ThreadLocal引用指向null而线程对象依然存在,那么此时Value是不会被回收的,这就发生了我们认为的内存泄露。比如,在使用线程池的时候,线程结束是不会销毁的而是会再次使用的,这种情形下就可能出现ThreadLocal内存泄露。
Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal进行get、set操作时会清除线程Map里所有key为null的value。所以最怕的情况就是,ThreadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,线程结束后被放回线程池中而不销毁,那么如果这个线程一直不被使用或者分配使用了又不再调用get/set方法,那么这个期间就会发生真正的内存泄露。因此,最好的做法是:在不使用该ThreadLocal对象时,及时调用该对象的remove方法去移除ThreadLocal.ThreadLocalMap中的对应Entry。