前言
通常情况下,我们创建的变量是可以被任何线程访问和修改的,但有时线程会希望有专属于自己的本地变量来进行一些操作。ThreadLocal类正是为了满足这一需求而被提出的。ThreadLocal类主要解决的就是对于变量,每个线程都可以有这个变量的副本,在对变量副本读写时都不会对其它的副本造成影响。
一、ThreadLocal示例
我们准备通过这个实例来论证ThreadLocal修饰的变量确实可以在不同的线程中有自己的变量副本。
我们设计一个UserSession类用来存储用户登录信息的bean。
再提供SessionCache将每个用户的session分别存储到它们的线程中
这里我们使用了ThreadLocal类,泛型用的UserSession。下面我们去测试一下,当不同的线程将它们中的用户信息存储到SessionCache类中的session中时,ThreadLocal能不能起到使每个线程都有session变量副本的效果,下面请看测试用例:
我们分别开启了t1线程和t2线程,t1和t2存储着不同的用户session,我们把它们都存储到SessionCache的静态ThreadLocal变量session中,然后再从session中取出并输出用户信息。
我们可以发现,session对于t1线程和t2线程输出的值是不同的,这也证明了ThreadLocal变量在每个线程中都有自己的变量副本。那接下来我们就来说一下它的原理吧。
二、ThreadLocal原理
我们在使用时肯定会思考这样一个问题,为什么用ThreadLocal修饰的变量,可以在不同的线程中创建自己的变量副本呢?我们来看下面一段Thread类中的源码:
从上面的源码中我们可以看到,Thread类中有ThreadLocalMap类型的变量,分别是threadLocals和inheritableThreadLocals变量
。而ThreadLocalMap是实际存放变量副本的容器,我们来看ThreadLocal类的源码,了解ThreadLocal是怎么把值存放到线程中的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//(1)
if (map != null)
map.set(this, value);//(2)
else
createMap(t, value);//(3)
}
//这里的t就是当前线程对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
当我们调用ThreadLocal的set方法时,他会先从当前线程的ThreadLocalMap中获取到map对象,然后判断这个map是否为null,如果没有就使用createMap()方法在当前线程中创建ThreadLocalMap对象并给map添加一个键值对,其中键就是当前的ThreadLocal类型,而值就是想要存储的value。如果已经被创建过(已经存储过其他的ThreadLocal变量),那就直接往map里添加键值对就行了。而ThreadLocal变量和ThreadLocalMap中的键值对也可以认为是多对多的关系,ThreadLocal变量可以往不同线程中的ThreadLocalMap中添加自己的变量副本,而ThreadLocalMap也可以接收不同的ThreadLocal变量。
//ThreadLocalMap的键值对类型
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
......
}
三、内存泄漏
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
我们可以看到,ThreadLocalMap中存放ThreadLocal对象的key是弱引用的,也就是说,如果这个key在没有外部强引用来引用它的时候,在下一次的gc时势必会被回收。而在作为key的ThreadLocal被回收后,value却因为是强引用而继续存在,因此ThreadLocalMap就会出现多个key为的Entry。这样的话在线程未结束前当前线程就会有一条指向value的强引用链导致导致这些Entry无法被回收,从而造成内存泄露。
不过ThreadLocalMap的设计者高瞻远瞩,已经想好了防护措施:在ThreadLocalMap执行get()、set()和remove()的时候都会去清除该线程中ThreadLocalMap中所有key为null的value。