Java线程之ThreadLocal的应用场景及原理
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Java线程之ThreadLocal的应⽤场景及原理
1.ThreadLocal是什么呢?
ThreadLocal与并发问题相关,每个ThreadLocal能够存放⼀个线程级别的变量,⽽它本⾝⼜能被多个线程共享使⽤,并且绝对的线程安全(数据隔离),它本⾝是为线程安全和某些特定场景的问题⽽设计的。
说到这是不是有点迷惑,反正笔者刚了解的ThreadLocal的时候是相当的迷惑,既然这个变量被多线程共享,怎么⼜说到数据隔离了呢,如果数据隔离的话,不要让线程共享变量不就⾏了。
其实,所说的共享只是共享⼀个变量引⽤⽽已,并不是共享⾥⾯的数据,⾥⾯的数据是分开存储的和访问的,所以实现了数据的隔离。
⽐如你哥哥结婚了,你们还住在⼀个房⼦⾥,但是你哥的媳妇始终是你哥的,永远不可能是你的。
房⼦就像⼀个ThreadLocal变量,你和哥哥是两个线程,你嫂⼦是数据,并且只属于你哥哥。
这下该清楚了吧。
很多⼈只是将它作为⼀种传参的⼯具,⽅法调⽤的时候,最初可能没有想那么多,有多少个参数就传递多少个参数,当发现某个⽅法需要增加⼀个参数时,我们总不能直接添加参数吧,如果你⾮要改,那你改个试试。
针对这个问题,可以通过ThreadLocal的⽅式很⽅便的进⾏参数传递。
注意使⽤的时候明⽩它的出⼝和⼊⼝是可控的,否则很容易造成内存泄漏。
2.应⽤场景
为了说明ThreadLocal的应⽤场景。
我们来看⼀个框架的样例。
Spring的事务管理器通过AOP切⼊业务代码,在进⼊业务代码前,会依据相应的事务管理器提取出相应的事务对象,假如事务管理器是DataSourceTransactionManager,就会从DataSource中获取⼀个连接对象,通过⼀定的包装后将其保存在ThreadLocal中。
⽽且Spring也将DataSource进⾏了包装,重写了当中的getConnection()⽅法,或者说该⽅法的返回将由Spring来控制,这样Spring就能让线程内多次获取到的Connection对象是同⼀个。
为什么要放在ThreadLocal⾥⾯呢?由于Spring在AOP后并不能向应⽤程序传递參数。
应⽤程序的每⼀个业务代码是事先定义好的,Spring并不会要求在业务代码的⼊⼝參数中必须编写Connection的⼊⼝參数。
此时Spring选择了ThreadLocal,通过它保证连接对象始终在线程内部,不论什么时候都能拿到,此时Spring很清楚什么时候回收这个连接,也就是很清楚什么时候从ThreadLocal中删除这个元素。
从Spring事务管理器的设计上能够看出。
Spring利⽤ThreadLocal得到了⼀个⾮常完美的设计思路,同⼀时候它在设计时也⼗分清楚ThreadLocal中元素应该在什么时候删除。
由此,我们简单地觉得ThreadLocal尽量使⽤在⼀个全局的设计上。
⽽不是⼀种打补丁的间接⽅法。
3.使⽤⽅式
public class ThreadLocalTest {
public static void main(String[] args) {
A a = new A();
for (int i = 0; i < 3; i++) {
final Integer num = i;
final String name = "sun"+i;
new Thread(){
public void run(){
try {
a.setId(num);
a.setname(name);
Thread.sleep(5000);
a.display();
} catch (InterruptedException e) {
} finally {
a.clear();
}
}
}.start();
}
}
}
class A{
public static ThreadLocal<String> name = new ThreadLocal<>();
public static ThreadLocal<Integer> id = new ThreadLocal<>();
public void setname(String name){
.set(name);
}
public void setId(Integer id){
A.id.set(id);
}
public void display(){
System.out.println("name: "+.get()+"\t"+"id: "+A.id.get());
}
public void clear() {
.remove();
A.id.remove();
}
}
//输出的结果
name: sun2 id: 2
name: sun0 id: 0
name: sun1 id: 1
通过输出结果可以看出,通过同⼀个ThreadLocal得到的变量是线程私有的。
4.源码分析(先放这占个位置,以后再更新,最近看源码都要看吐了。
)
接下来,分析⼀下ThreadLocal变量中最常⽤的⽅法set(),get(),remove()的源码,来看看它是如何做到线程私有的呢。
a)set()⽅法:
⽅法很简单,⾸先获取了当前的线程,然后根据当前的线程获取了它的⼀个threadLocals变量,这个变量是ThreadLocalMap类型的。
最后,如果map不存在则创建⼀个。
b) get()⽅法。
同样的套路,如果不存在数据则返回⼀个初始值。
c)remove()⽅法。
详细的分析以后再补充。
5.注意事项
我们往ThreadLocal存的数据都会保存在线程的⼀个map中,如果线程不⽤了,那么存放的数据⾃然跟着销毁了。
可是,像jvm中的线程是复⽤的,这就会出现⼀个问题,数据没⽤了,但是依然保存在内存中清理不掉,数据的⽣命周期变得不可预测,极端的情况与jvm的⽣命周期⼀致,这会造成严重的内存泄漏,进⽽导致内存溢出的情况。
这是使⽤ThreadLocal的⼀个⼤坑。
所以在使⽤过程中,⼀定要把ThreadLocal变量安排的明明⽩⽩,及时清理数据。
6.ThreadLocal与Synchronized的区别:
ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
Synchronized利⽤锁机制,以时间换空间的⽅式,ThreadLocal则以空间换时间,⽤于线程间的数据隔离。