读ThreadLocal源码学习内存泄漏问题

线程安全问题

Thread代码如下,ThreadLocal.ThreadLocalMap threadLocals 这个为 Thread的属性

1
2
3
4
public
class Thread implements Runnable {

ThreadLocal.ThreadLocalMap threadLocals = null;

下面是ThreadLocal的set方法,每次在set的时候都会取出对应的线程的threadLocals属性,所以这里不存在线程并发的问题, ThreadLocal.ThreadLocalMap中维护了一个类似于hashmap的数组链表结构,注意这里的map存放的是当前线程下的多个ThreadLocal,并不是多个线程的ThreadLocal,这里一定要搞清楚.

1
2
3
4
5
6
7
8
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使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

set(null)是把ThreadLocalMap的value赋值为null,并没有删掉这个K-V这个元素
而remove则是把K-V这个元素都删掉了,两个不同的东西,不要弄混了

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

如下是遍历ThreadLocalMap中的key做remove操作,后续还有对应的rehash等操作,如果是set(null)则只是把value置成null,但是该key还存在,所以属于内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}

弱引用

如下所示, ThreadLocalMap中的key(ThreadLocal)为弱引用,当该ThreadLocal没有被强引用时,如果出现了GC则会回收该key,使得key变为null,其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

1
2
3
4
5
6
7
8
9
10
11
static class ThreadLocalMap {

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
zhang dong wechat
关注我的微信来交流技术问题吧!