为什么使用
- 对线程不安全的类包装一层, 使之看起来是线程安全(实际上使用不是同一个实例), 典型的是SimpleDateFormat
- 线程私有的局部变量, 比如统计线程运行任务次数
线程局部变量如何实现
三叉戟 Thread, ThreadLocal, ThreadLocalMap
Thread对象中成员变量threadLocals(普通局部变量表), inheritableThreadLocals(可从父线程继承局部变量表, 线程初始化拷贝过来, 初始化之后不会同步)
ThreadLocalMap 以ThreadLocal为key, 值为value, 采用弱引用包装(gc)
引用关系为Thread --> ThreadLocalMap --> ThreadLocal
由于threadLocals是Thread的成员变量, 所以不同的线程拿到的ThreadLocalMap不同(ThreadLocal.getMap()), 拿到的ThreadLocal变量也就不同, 也就是实现了线程局部变量
内存泄露分析
先说结论, ThreadLocal内存泄露与弱引用无关
ThreadLocal内存泄露分为两个层面, 一是threadLocals 局部变量表, 二是ThreadLocal局部变量
我们假如线程一直不被回收(多见于不允许核心线程超时的线程池中的核心线程)
- 对于threadLocals泄露情况, 线程中有特别多的ThreadLocal实例, 造成threadLocals特别大, gc回收不了
- ThreadLocal本身泄露
ThreadLocalMap中key为弱引用, gc每次都会回收key, 如果不调用ThreadLocal.remove 会造成value一直回收不了, 导致内存泄露(有一种说法是因为弱引用, 但是如果使用强引用, 不调用remove方法, 泄露只会更严重)
#staleSlot 为当前remove的ThreadLocal在threadLocals中的索引
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
# 1. 将value置空, 去掉对value的强引用, 帮助值gc
# 2. 将threadLocals中的slot置空, 帮助gc
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
# 顺带将已经gc回收了的ThreadLocal以及value删掉
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
# rehash, 将之前由于冲突的Entry归位
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}