在java多线程编程中,ThreadLocal是一个经常被大家提及的知识点,比如ThreadLocal的作用是什么?常用的使用场景有哪些?实现原理是什么等等,只有彻底理解透彻,才能游刃有余地应对,本篇文章就从这几个方面分析理解ThreadLocal:
1、ThreadLocal是什么
2、ThreadLocal的作用是什么
3、ThreadLocal源码分析
4、ThreadLocal内存泄漏问题
一、ThreadLocal是什么
- ThreadLoal 变量,线程局部变量,可以这样理解:里面的对象是线程独有的,相对其它线程是隔离的。
- ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
- 总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
二、ThreadLocal有什么作用
- 线程间数据隔离;
- 数据库连接,Session会话管理;
- Spring 声明式事务,用于存储线程事务信息,保证同一个线程拿到的都是同一个连接(Connection)。
三、ThreadLocal源码分析
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void remove(){
ThreadLocalMap map = getMap(Thread.currentThread());
if (map != null) {
map.remove(this);
}
}
set方法:
从set方法我们可以看到,首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。
OK,到这一步了,相信你会有几个疑惑了,ThreadLocalMap是什么,继续往下看。先来看ThreadLocalMap。
我们可以看到ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,它继承了弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。
getMap方法:
调用当期线程t,返回当前线程t中的成员变量threadLocals。而threadLocals其实就是ThreadLocalMap。
get方法:
首先获取当前线程,然后调用getMap方法获取一个ThreadLocalMap,如果map不为null,就使用当前线程作为ThreadLocalMap的Entry的键,获取相应的的值,如果没有那就设置一个初始值。
通过内部源码分析原理是不是很简单,总结一下:
(1)每个Thread维护着一个ThreadLocalMap的引用;
(2)ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储;
(3)ThreadLocal创建的副本是存储在自己的threadLocals中的,也就是自己的ThreadLocalMap;
(4)ThreadLocalMap的键值为ThreadLocal对象,而且可以有多个threadLocal变量,因此保存在map中;
(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialValue()方法;
(6)ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
四、ThreadLocal内存泄漏问题
分析ThreadLocal内存泄漏问题之前我们先看一张图,有助于理解为什么会存在内存泄漏:
上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap;
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的;
3、ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收(强引用则不会被回收)。
那么问题来了,如果ThreadLocal=null 导致它的value(ThreadLocalMap)无法被访问到,故不能被回收,因此造成内存泄漏。
解决办法:
ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。
所以在使用完ThreadLocal后,手动调用 remove操作,避免出现内存溢出情况。
objectThreadLocal.set(userInfo);
try {
// ...
}
finally {
objectThreadLocal.remove();
}