简单整理下ThreadLocal的原理,以及它需要注意的内存泄漏。
ThreadLocal原理
ThreadLocal不多介绍,可看作线程内的局部变量(这个比喻很贴切)。我们平时声明的局部变量的范围一般是方法内的,而ThreadLocal变量的范围是整个线程。
我们先来看一段代码demo:
public class Test {
//可看作线程内声明的局部变量
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void a(){
//设置线程内局部变量值为1
threadLocal.set("1");
}
public void b(){
//获取线程内局部变量值
System.out.println(threadLocal.get()); //输出:1
//设置线程内局部变量值为2
threadLocal.set("2");
//获取线程内局部变量值
System.out.println(threadLocal.get()); ////输出:2
}
public static void main(String[] args) {
Test test = new Test();
test.a();
test.b();
}
}
通过方法a()
设置了线程局部变量ThreadLocal
的值,然后再另一个方法b()
中获取并修改了它。由于调用时方法a()
和b()
都在同一线程中,所以可以成功获取和修改threadLocal
问题:那ThreadLocal是如何做到,使变量的值的使用范围是整个线程的呢?
这主要得益于线程Thread
类中的一个成员变量:ThreadLocalMap
。这个Map
的键值对是<ThreadLocal,Object>
,key
是ThreadLocal对象,value
是该ThreadLocal对象设置的值。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap
可看作保存着该线程内所有的 “线程局部变量”的集合。每个线程都维护着自己的线程局部变量集合:ThreadLocalMap
:
当我们设置一个ThreadLocal的值myThreadLocal.set("1")
时,其实就是在往该线程的成员变量ThreadLocalMap
中添加myThreadLocal-1
的一个元素:
当我们获取一个ThreadLocal
的值myThreadLocal.get()
时,其实就是从ThreadLocalMap
中获取key为myThreadLocal
的entry的值:
内存泄漏
ThreadLocal
使用不当是容易发生内存泄漏的。原因在于,我们设置完ThreadLocal
的值后,该线程如果还在运行,ThreadLocalMap
中该ThreadLocal
所在的Entry不会被回收,一直在内存中存在。即存在这种引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry
为了解决这个问题,ThreadLocalMap
中的key被设置为了弱引用,即一段时间后没被使用的话,key值将被GC垃圾回收机制回收。而ThreadLocal
的get()
、set()
执行时,会检查ThreaLocalMap
中key值为null的Entry,将value去除。
但是这仍然没有完全解决内存泄漏的问题,原因在于,如果该线程的再也没有执行ThreadLocal
的get()
、set()
方法,则value仍然会一直存在内存中,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
所以,为了避免内存泄漏,我们最好在使用完ThreadLocal
后,手动remove()
掉它