在多线程中为了避免线程安全问题,常用的一种方式就是引入ThreadLocal变量,为何这种方式会引发线程安全问题呢?
首先我们来看一下ThreadLocal是如何实现保存线程私有变量的原理:
ThreadLocal里面定义了一个内部类ThreadLocalMap
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
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
....
}
这个ThreadLocalMap是关键,再查看Thread源码,我们可以看到
每个Thread持有一个初始值为null的ThreadLocalMap变量
在ThreadLocal变量调用get、set方法时,会对当前线程的ThreadLocalMap进行初始化,那么这时会做什么操作呢
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可以看到ThreadLocal变量将自身作为参数构造了线程持有的ThreadLocalMap,而这里面初始化了一个Entry数组类型的table,并且ThreadLocal变量是作为key使用的。是不是有点眼熟,不过这个Entry是ThreadLocalMap里的一个内部类Entry,从上面源码中可以看到Entry继承了WeakReference,这是造成内存泄漏的关键。WeakReference指的弱引用,弱引用的定义是,当jvm执行内存回收时发现该某个对象只有弱引用时,会被回收。那么问题来了,当某个生存周期长的线程运行过程中,某个定义的ThreadLocal强引用变成了null,只剩线程里的ThreadLocalMap里持有的弱引用,gc发生时,该Entry的key指向的对象将被回收,可是强引用value指向的对象是无法回收的,就造成了内存泄漏。
解决方法:当某个ThreadLocal变量不再使用时,记得threadLocal.remove()
,删除该key。