引用结构
每个线程都有一个ThreadLocal.ThreadLocalMap
类型的threadLocals
属性, ThreadLocalMap
中使用Entry
来存储线程相关变量,Entry
的key
是对ThreadLocal
的一个弱引用,value
则是Object
类型的线程变量。如下图:
内存泄漏
从引用结构上可以看到,若ThreadLocal
没有被GC Roots
强引用时,在垃圾回收后,ThreadLocal
则会被回收, Entry的key无引用。若线程一直不退出,则value
就一直不会被回收,造成内存泄漏。如下图:
泄露演示
public class ThreadLocalCode {
public static void main(String[] args) {
// 第一步:模拟ThreadLocal执行后,丢失GCRoots的强引用
setThreadLocal();
// 第二步:手动gc(断点位置1:gc前查看Thread对象)
System.gc();
// 第三步: gc后再次查看Thread对象,(断点位置2)
System.out.println();
}
public static void setThreadLocal() {
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("123");
}
}
断点1位置的Thread#ThreadLocalMap状态
由图可见,此时Entry的key(即referent)和value均存在。
断点2位置的Thread#ThreadLocalMap状态
由图可见,此时的key被回收了,只要当前线程不结束,这个Entry会因为没有key而找不到,所以会一直回收不了,由此可知,像setThreadLocal这种方式在项目中多次使用的结果。
正确使用
相信看了上面的分析,大家应该都知道怎么使用ThreadLocal
了。如果项目中使用的Thread
不会终止,我们只需要保证ThreadLocal
变量不要被回收掉即可,我们知道ThreadLocal
变量一是被Entry的key引用,但这是一个弱引用,所以我们可以再加一个强引用,比如将ThreadLocal
变量定义为静态变量。同时我们也要保持一个良好的编码习惯,在使用完ThreadLocal
变量后手动remove一下,做到万无一失。