ThreadLocal
1.简介
ThreadLocal是为了解决不同线程在处理同一个类的时候,出现变量不安全的问题。每个线程会保存一个变量的副本互相不影响,所以是对线程隔离的。
2.ThreadLocal使用方法
public class ThreadLocalTest() {
private static ThreadLocal<String> strLocal = new ThreadLocal();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
strLocal.set("ThreadLocal---1");
TimeUnit.SECONDS.sleep(2);
System.out.println(strLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
strLocal.set("ThreadLocal---2");
TimeUnit.SECONDS.sleep(1);
System.out.println(strLocal.get());
}
}).start();
}
}
运行可以看出,两个线程分别存储了自己的strLocal,拿到的值也是各自存储的样子。
3.原理分析
在执行threadLocal.set()方法的时候,会在每个线程类Thread内部,去生成每个线程独立的ThreadLocalMap对象,这个对象就是用来具体存值的。存值采用的是key-value形式的Entry,这个Key就是对ThreadLocal的引用,类似于指针。get()方法也比较类似,会根据当前线程的id去获取线程中的ThreadLocalMap对象,然后根据ThreadLocal的引用去拿value。所以,就实现了线程隔离。
4.ThreadLocal内存泄漏
ThreadLocal是存在内存泄漏风险的。原因是:因为ThreadLocalMap中存的Key(存在Entry中的key-value)是指向ThreadLocal对象的指针(弱引用),如果在某个时间,ThreadLocal被使用完了(没有指向ThreadLocal的强引用),ThreadLocal就会GC回收了,也就是说,ThreadLocalMap中的Key变成了null,但是value却依然存在,这个Entry中的value就会造成内存泄漏。为什么不适用强引用?因为使用了强引用,在使用ThreadLocal没有被对象强引用的时候,但是此时线程还是存在,那么就会有堆中的ThreadLocal本身的内存泄漏了,因为Entry中还是有对他的强引用,所以一直都不会被回收。
以上是总结的ThreadLocal内存使用图,可以简单点的解释到底是怎么个布局。
其中,需要注意的是,ThreadLocalMap的生命周期是和Thread保持一致的,所以只要这个线程一直运行,那么ThreadLocalMap就会一直存在。但是线程对于每个ThreadLocal的强引用却不会一直存在,如果这个强引用失效了,那么堆中ThreadLocal就会在下次GC被回收掉,这个时候,Entry中的弱引用就是null了。为了避免这个问题,最好的方式就是每次含有ThreadLocal的类在get()完之后,手动remove掉。 同时,在调用ThreadLocal的get、set、remove方法的时候也会去检查key为null的Entry,并将value释放掉。但是如果设置了一个key之后一直不再使用set、get、remove,那么就会有内存泄漏问题了。
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.refersTo(null)) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 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) {
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)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}