ThreadLocal的原理和内存泄露
ThreadLocal的用法
ThreadLocal可以保证一个线程共享一个变量,例如
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args){
Thread thread1 = new Thread(()->{
threadLocal.set("hello");//线程1设值
});
Thread thread2 = new Thread(()->{
System.out.println(threadLocal.get());//线程2取值
});
thread1.start();
try {
thread1.join();//保证线程1完成了设值
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
thread2.start();//线程2取值,输出结果null
}
可以看出两个线程不共享资源,这是如何实现的呢?
原理
每个线程里有个map,k和v的映射关系是:通过threadlocal,映射到value,也就是说。threadlocal.set执行的步骤是:
- 获取当前线程实例thread
- 从thread中获取threadlocalmap
- 把threadlocal实例->value的映射关系放进threadlocalmap
取值的步骤是:
- 获取当前线程实例thread
- 从thread中获取threadlocalmap
- 从threadlocalmap中根据threadlocal获取value
内存泄露
为什么会有内存泄露的可能呢
首先啥是内存泄露?就是内存中有一个对象,无法被回收。
如果我们执行这个命令希望不再使用threadlocal:
threadlocal=null;
我们预期的结果是这个变量应该要被回收。但是threadlocal自打它出生开始,就有其他的变量持有这个引用——也就是我们刚刚说的threadlocalmap,这个存在于线程内部的变量,一直持有这个threadlocal,通过threadlocal = null
无法把全部的引用清空,所以直到整个线程结束之前,无法被垃圾回收。
java源码巧妙地解决了这个问题,有一种引用形式是弱引用:
弱引用:下次垃圾回收就给回收了
threadlocalmap对threadlocal的引用方式就是弱引用。threadlocal的对象的引用就只有threadlocal
和threadlocalmap内部的引用,如果执行了threadlocal=null
之后threadlocal的对象只剩一个弱引用,在下次垃圾回收的时候这个对象就会被清理了。
隐藏的问题
正如刚才所说,是通过弱引用对threadlocal进行持有的,代码如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry是个弱引用持有了key,也就是threadlocal,threadlocal对象在threadlocal=null
之后就会被回收,但是value这个对象作为Entry的成员变量一直被Entry对象持有,并且由于threadlocal对象已经被回收了,无法通过这个key来找到这个value对象,所以同样在线程结束之前无法回收value的值。
解决办法
如果有一个threadlocal不用了,要使用remove方法。
threadlocal.remove();