ThreadLocal
类提供了四个方法:
get():返回此线程局部变量的当前线程副本中的值。
initialValue():返回此线程局部变量的当前线程的“初始值”,默认返回null,供子类重写。
remove():移除此线程局部变量当前线程的值。
set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
ThreadLocal
,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,其实意思差不多。
ThreadLocal
和线程同步机制不同,线程同步机制主要采用synchronized
关键字和J.U.C
中的Lock
对象来实现,也就是加锁,加锁的目的是为了能让多个线程安全的共享一个变量。
而ThreadLocal
为每个线程创建了自己独有的变量副本,也就是说,线程可以随意的操作自己拥有的变量副本,不会对其他线程的变量副本产生影响,实现了线程隔离。
ThreadLocal
类中的静态内部类ThreadLocalMap
,这个Map
是ThreadLocal
实现线程隔离的精髓。
Thread
类中有这样子一个成员变量:
/* 与此线程相关的ThreadLocalMap */
ThreadLocal.ThreadLocalMap threadLocals = null;
因此,一个线程对应有自己单独的一个ThreadLocalMap
。
所以ThreadLocal
才可以实现线程隔离(Thread
,ThreadLocal
,ThreadLocalMap
三者的对应关系如图所示)
ThreadLocal
的set
方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
通过ThreadLocal
的set
方法看出,ThreadLocalMap
的
结构中,key
存储的是ThreadLocal
本身,而value
则是实际存储的值,也就是说,在ThreadLocalMap
里存储了当前ThreadLocal
所复制的变量副本。
ThreadLocal
本身不存储值,在使用中,ThreadLocal
是作为一个key
,从ThreadLocalMap
获取值,从ThreadLocal
的get
方法中也可以看出来:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap
的静态内部类Entry
:
static class Entry extends WeakReference<ThreadLocal>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap
使用静态内部类Entry
实现
存储,而Entry
继承WeakReference
类,所以ThreadLocalMap
中的key
其实是ThreadLocal
的一个弱引用。
正因为ThreadLocalMap
以ThreadLocal
的弱引用作为key
,在这个ThreadLocal
没有外部强引用的时候,会被GC。这时候,ThreadLocalMap
会出现一个key
为null
的Entry
,理所当然的,这个Entry
的value
将永远没办法被访问到。
在这种情况下,如果当前工作线程一直没有结束,那这个key
为null
的value
因为被Entry
强引用,而Entry
被当前线程的ThreadLocalMap
强引用,导致这个value
永远无法被GC,造成内存泄漏。
虽然在ThreadLocalMap
的设计中,已经考虑到这种情况的发生:
ThreadLocalMap
的cleanSomeSlots()
,expungeStaleEntry()
方法都能清除key
为null
的value
。- 在
ThreadLocal
的set()
,get()
,remove()
方法中,都会调用cleanSomeSlots()
或expungeStaleEntry()
来清除ThreadLocalMap
中所有key
为null
的value
。
但是这种被动清除的方式并不是在所有情况下都能保证不出现内存泄漏,反例:
- 如果
ThreadLocal
的set()
,get()
,remove()
方法没有被调用,就会导致value
的内存泄漏。- 用
static
修饰的ThreadLocal
,导致ThreadLocal
的生命周期和持有它的类一样长,意味着这个ThreadLocal
不会被GC。这种情况下,如果不手动删除,Entry
的key
永远不为null
,弱引用就失去了意义,理所当然的无法通过调用cleanSomeSlots()
,expungeStaleEntry()
方法清除value
,如果当前线程结束了,就导致了Entry
的内存泄漏。
看到这里可能会有这种感觉:内存泄漏都是因为key
的弱引用造成的啊?那为什么不用强引用呢?
表面上看起来好像是这样子,那试想一下,如果key
使用强引用,可以有这样子的对比:
key
使用强引用:在当前ThreadLocal
没有外部强引用时,ThreadLocalMap
的Entry
还保持着ThreadLocal
的强引用,ThreadLocal
不会被GC。如果没有手动删除,并且当前线程结束了,就导致了Entry
的内存泄漏。(有点类似用static
修饰ThreadLocal
的情况)key
使用弱引用:在当前ThreadLocal
没有外部强引用时,ThreadLocalMap
只保持着ThreadLocal
的弱引用,无论有没有手动删除,ThreadLocal
都会被GC,只要下一次cleanSomeSlots()
,expungeStaleEntry()
被调用,value
就会被清除,否则也会引起内存泄漏。通过这样子的对比,我们可以得出:由于ThreadLocalMap
的生命周期和Thread
一样长,如果没有手动删除,都有可能会导致内存泄漏,但是弱引用多了一层保障:使用弱引用时,ThreadLocal
无论如何都不会内存泄漏,只要下一次cleanSomeSlots()
,expungeStaleEntry()
被调用,value
就会被清除。
通过对比我们也可以看出:ThreadLocal
引起的内存泄漏的根源,是因为ThreadLocalMap
的生命周期跟Thread
一样长,并且在线程结束之前,没有删除key
,并不是因为弱引用。
ThreadLocalMap
解决哈希冲突的方法:“开放定址法”。参考文章:
http://cmsblogs.com/?p=2442
http://www.cnblogs.com/onlywujun/p/3524675.html