ThreadLocal
ThreadLocal是一个用于实现线程数据隔离的一个类,每个线程访问时,通过Get、Set方法都会产生一个属于该线程的局部变量副本,当线程结束时,ThreadLocal及变量随着线程一起被回收。
ThreadLocal的作用
总的来说,ThreadLocal有三大用途:
1. 保存线程上下文信息,在任何地方都可以获取(通过static关键字)
我们可以使用static关键字,在任意地方都可以对该ThreadLocal进行获取、设置。
例如Spring中的事务,用ThreadLocal存储了连接对象,保证一次事务的所有操作都在同一连接上。
2. 线程安全,避免某些情况下需要保持同步而带来的性能损失
3. 线程之间数据隔离
ThreadLocal的原理
ThreadLocal虽然叫线程局部变量,但是它不存储任何数据,它只是一个壳子,真正的存储结构是在ThreadLocal中有一个ThreadLocalMap的一个内部类,而这个内部类却被Thread定义为了成员变量。
而ThreadLocal本身并不存储值,它只是作为key存储在Thread对象中的ThreadLocalMap中,而value则是我们存储的变量。
那么,既然真正存储数据的是Thread对象中的ThreadLocalMap,每个线程都是自己的Thread对象,那么也就达成了线程私有变量以及数据隔离。
public void set(T value) {
// 返回当前 ThreadLocal 所在的线程
Thread t = Thread.currentThread();
// 返回当前线程持有的map
ThreadLocalMap map = getMap(t);
if (map != null) {
// 如果 ThreadLocalMap 不为空,则直接存储<ThreadLocal, T>键值对
map.set(this, value);
} else {
// 否则,需要为当前线程初始化 ThreadLocalMap,并存储键值对 <this, firstValue>
createMap(t, value);
}
}
源码解析:
- Set() 方法
主要有几个步骤:
- 获取当前线程
- 将当前线程传入getMap()方法,获取ThreadLocalMap对象
- 设置 key 为 [当前ThreadLocal对象],value为我们设定的值。
- 如果Map不存在,则创建一个Map
这时候,我们肯定纠结,这个Map到底是什么?我们接着看:
进入getMap() 方法:
这是我们可以看到,该方法返回了当前线程的 threadLocals 属性,那我们再看看是什么:
原来是在Thread对象中,看到这里是不是有种明悟,原来:
- set() 方法中,首先获取当前线程。
- 获取该线程对象中的 threadLocals 属性。
ThreadLocalMap map = getMap(t))
- 如果该属性存在,则往其中添加元素。
map.set(this, value)
- 而不存在的话,则初始化该属性 threadLocals,并将值添加进去作为初始值
createMap(t, value);
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们都知道,每个线程在Java内部都表现为一个Thread对象,而每个Thread对象都拥有 threadLocals 属性,在线程调用了 set() 方法后,会初始化该属性,并且将本线程所属的元素存入进去,这样就造成了数据隔离,每个线程有自己的数据。
实战验证:
- 没有使用ThreadLocal的情况:
我们都知道,SimpleDateFormat因为内部使用了日历对象Calendar,导致不能将其作为线程共享对象,会引发线程安全问题。
(因为只有一个Calendar实例,而多线程共享该实例 )
结果(线程1及线程2运行时设置的时间被线程3覆盖,导致出现了错误的结果):
- 使用ThreadLocal:
结果(因为每个线程都存着自己的那份SimpleDateFormat对象,所以不会出现并发情况):
Debug:
线程1拥有的:
线程2拥有的:
线程3拥有的:
可以看到,每个线程都拥有自己的SimpleDateFormat对象。
ThreadLocal内存泄漏
ThreadLocal内存泄漏指的是:ThreadLocal对象被回收了,但是线程内的ThreadLocalMap成员与线程一样继续存在着,它不会回收,就出现了一个现象,就是ThreadLocalMap的key没了,Map没有了指向,但value还在,key的引用一直在就造成了内存泄漏。
而为什么ThreadLocal对象会被回收呢,因为它是弱引用。它的生命周期更短,会在一次GC后回收掉。
所以使用其最好的方式是:用完了,将其remove掉,从Thread.threadLocals(Map)中删除即可。
文章来源:https://www.toymoban.com/news/detail-661103.html
总结
总的来说,ThreadLocal通过避免并发保证数据安全,将数据绑定到Thread对象上,保证了数据隔离。而在使用完毕后,必须手动将值进行remove(),否则,当线程一直运行时,容易出现内存泄漏风险。文章来源地址https://www.toymoban.com/news/detail-661103.html
到了这里,关于【ThreadLocal详解】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!