ThreadLocal工作原理
目录一、官方文档描述
从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。
ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
二、为什么使用ThreadLocal
我们的系统中同时会有很多用户请求,那每个请求都带有用户信息,我们知道通常都是一个线程处理一个用户请求,我们可以把用户信息丢到Threadlocal里面,让每个线程处理自己的用户信息,线程之间互不干扰。
我们程序在处理用户请求的时候,通常后端服务器是有一个线程池,来一个请求就交给一个线程来处理,那为了防止多线程并发处理请求的时候发生串数据,比如AB线程分别处理用户A和用户B的请求,A线程本来处理用户A的请求,结果访问到用户B的数据上了,造成了数据紊乱。
所以在A线程中要达到的效果是只能够操作A线程中的数据,而不应该去操作不属于本线程之内的数据,而ThreadLocal就可以达到这样的一种效果。
效果如下所示:
上述图中场景伪代码说明:
//存放用户信息的ThreadLocal
private static final ThreadLocal<UserInfo> userInfoThreadLocal = new ThreadLocal<>();
public Response handleRequest(UserInfo userInfo) {
Response response = new Response();
try {
// 1.用户信息set到线程局部变量中
userInfoThreadLocal.set(userInfo);
doHandle();
} finally {
// 3.使用完移除掉
userInfoThreadLocal.remove();
}
return response;
}
//每个线程执行的业务逻辑处理
private void doHandle () {
// 2.实际用的时候取出来
UserInfo userInfo = userInfoThreadLocal.get();
// 查询用户资产
queryUserAsset(userInfo);
}
2.1、案例
感受一下ThreadLocal 线程隔离的特点
不使用ThreadLocal
对应的结果如下所示:
从结果可以看出多个线程在访问同一个变量的时候出现的异常,线程间的数据没有隔离。
使用ThreadLocal
下面我们来看下采用 ThreadLocal 的方式来解决这个问题的例子。
将结果打印到控制台,查看输出信息,可以看到线程之间实现了隔离。
对应的输出结果:
三、ThreadLocal和syncronized关键字区别
虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题, 不过两者处理问题的角度和思路不同。
synchronized | ThreadLocal | |
---|---|---|
原理 | 同步机制采用’以时间换空间’的方式, 只提供了一份变量,让不同的线程排队访问 | hreadLocal采用’以空间换时间’的方式, 为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
虽然使用ThreadLocal和synchronized都能解决问题,但是使用ThreadLocal更为合适,因为这样可以使程序拥有更高的并发性。
因为synchronized关键字关注的是多线程对共享变量的操作;而ThreadLocal关注的是是共享变量副本的问题,我们希望的是在线程上下文中都可以使用到这个线程中的共享变量的副本,在不使用的时候将其移除掉即可,那么就可以使用ThreadLocal来进行解决。
四、数据结构
五、源码分析
set方法
对应的流程如下所示:
-
1、首先获取当前线程,并根据当前线程获取一个ThreadLocalMap;
-
2、如果获取的ThreadLocalMap不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key) ;
-
3、 如果ThreadLocalMap为空,则给该线程创建 ThreadLocalMap,并设置初始值;
get方法
具体分析下执行流程:
- 1、首先获取当前线程, 根据当前线程获取得到ThreadLocalMap;
- 2、如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则执行4步骤;
- 3、如果e不为null,则返回e.value,否则执行4步骤;
- 4、ThreadLocalMap为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的ThreadLocalMap;