当前位置:网站首页>ThreadLocal原理与使用
ThreadLocal原理与使用
2022-07-04 17:05:00 【什么是快乐】
ThreadLocal原理与使用
最近看到公司的系统管理组件中获取人员信息的一段代码:
//获取登录用户信息
UserLoginInfo userLoginInfo = ThreadLocalUtils.getUserLoginInfo();
getUserLoginInfo()方法本以为是通过token解析或者是通过token查redis。但是点进去却发现是从ThreadLocal中获取登陆信息?
private static final ThreadLocal<Map<String, Object>> local = ThreadLocal.withInitial(HashMap::new);
public static UserLoginInfo getUserLoginInfo() {
return get(Constants.USER_LOGIN_INFO);
}
/** * 从ThreadLocal中获取值 * * @param key 键 * @param <T> 值泛型 * @return 值, 不存在则返回null, 如果类型与泛型不一致, 可能抛出{@link ClassCastException} * @see Map#get(Object) * @see ClassCastException */
public static <T> T get(String key) {
return ((T) local.get().get(key));
}
一番百度学习后发现Threadlocal是线程间做数据隔离的。这时我又想起之前学习的volatile保证数据变量的可见性。数据为什么又隔离又可见。区别是什么?不禁混乱,下面是个人的梳理
volatile
volatile是Java的一个关键字,一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,就具备了两层语义:保证了不同线程对这个变量进行操作时的可见性和禁止了指令重排序。
Java中变量是存放在主内存中,线程并不能直接读取和操作主内存的变量,而是会将变量读到工作内存中进行操作。而volatile的作用是保证所有线程能第一时间获取变量的最新值。但是要注意的是,对volatile修饰的变量的更改操作并不是原子性的。因为更改操作包括读变量,修改变量和写入工作内存。而volatile只是保证 了读变量是读的最新值。所以,volatile无法保证对变量的任何操作都是原子性的。
ThreadLocal
ThreadLocal会为每个使用该变量的线程提供独立的变量副本,保证各个线程之间的隔离。探究底层,ThreadLocal是通过静态内部类ThreadLocalMap实现的。
下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap
和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。
set方法
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
get方法
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null
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();
}
threadLocal 和threadLocalMap的关系如下图所示。
threadlocal内存泄漏问题
上图我们可以看到,threadLocal最终还是调用的threadlocalMap类的方法,而threadlocalMap的内部其实是一个Entry数组
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key是ThreadLocal的弱引用。如果 key threadlocal 为 null 了,这个 entry就清除了。
当threadlocal为null或者被垃圾回收后,但ThreadLocalMap(threadlocals)是线程的内部属性,它的生命周期与所属线程同步。并不会被垃圾回收。所以此时ThreadLocalMap(threadlocals)就会出现一个现象:key为空,但是value值还在。这就是内存泄漏问题。
避免方法
在调用ThreadLocal最后一定要调用remove()方法。
接着文章开头的从threadLocal中获取用户登陆信息。发现项目中有一个拦截器,每次请求都会将token中获得的用户信息存入threadLocal中。这样后面的service层代码就可以直接通过threadlocal获取,而不需要通过参数传递。
/** * @author LSWXXY-WQ */
@Slf4j
public class UserInterceptor extends HandlerInterceptorAdapter {
@Autowired
UserService userService;
@Autowired
MenuService menuService;
@Resource
private AuthorizationService authorizationService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取token
String token = request.getHeader(Constants.USER_TOKEN_HEADER);
//通过token获取用户信息并放入ThreadLocal中
if (StringUtil.isBlank(token) ) {
UserloginInfo userloginInfo = userService.getLoginInfo(token)
ThreadLocalUtils.setUserLoginInfo(userloginInfo);
}
//后续一系列校验等等
.....
.....
.....
.....
return super.preHandle(request, response, handler);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//业务结束后清空threadlocal
ThreadLocalUtils.clear();
super.afterCompletion(request, response, handler, ex);
}
最后附上项目的threadLocalUtils吧。核心还是对ThreadLocal的get、set的使用
public final class ThreadLocalUtils {
private ThreadLocalUtils() {
}
private static final ThreadLocal<Map<String, Object>> local = ThreadLocal.withInitial(HashMap::new);
/** * @return threadLocal中的全部值 */
public static Map<String, Object> getAll() {
return local.get();
}
/** * 设置一个值到ThreadLocal * * @param key 键 * @param value 值 * @param <T> 值的类型 * @return 被放入的值 * @see Map#put(Object, Object) */
public static <T> T put(String key, T value) {
local.get().put(key, value);
return value;
}
/** * 删除参数对应的值 * * @param key * @see Map#remove(Object) */
public static void remove(String key) {
local.get().remove(key);
}
/** * 清空ThreadLocal * * @see Map#clear() */
public static void clear() {
local.remove();
}
/** * 从ThreadLocal中获取值 * * @param key 键 * @param <T> 值泛型 * @return 值, 不存在则返回null, 如果类型与泛型不一致, 可能抛出{@link ClassCastException} * @see Map#get(Object) * @see ClassCastException */
public static <T> T get(String key) {
return ((T) local.get().get(key));
}
/** * 从ThreadLocal中获取值,并指定一个当值不存在的提供者 * * @see Supplier * @since 3.0 */
public static <T> T get(String key, Supplier<T> supplierOnNull) {
return ((T) local.get().computeIfAbsent(key, k -> supplierOnNull.get()));
}
/** * 获取一个值后然后删除掉 * * @param key 键 * @param <T> 值类型 * @return 值, 不存在则返回null * @see this#get(String) * @see this#remove(String) */
public static <T> T getAndRemove(String key) {
try {
return get(key);
} finally {
remove(key);
}
}
public static void setUserLoginInfo(UserLoginInfo loginInfo) {
put(Constants.USER_LOGIN_INFO, loginInfo);
}
public static UserLoginInfo getUserLoginInfo() {
return get(Constants.USER_LOGIN_INFO);
}
public static void setAppLoginInfo(AppLoginInfo loginInfo) {
put(Constants.APP_LOGIN_INFO, loginInfo);
}
public static AppLoginInfo getAppLoginInfo() {
return get(Constants.APP_LOGIN_INFO);
}
public static void setAppName(String appName) {
put(Constants.APP_NAME, appName);
}
public static String getAppName() {
return get(Constants.APP_NAME);
}
public static void setSysLog(SysLog sysLog){
put(Constants.OPERATE_LOG,sysLog);
}
public static SysLog getSysLog(){
return get(Constants.OPERATE_LOG);
}
}
边栏推荐
- 李迟2022年6月工作生活总结
- People in the workplace with a miserable expression
- Blue bridge: sympodial plant
- 未来几年中,软件测试的几大趋势是什么?
- "In Vietnam, money is like lying on the street"
- I always thought that excel and PPT could only be used for making statements until I saw this set of templates (attached)
- Once the "king of color TV", he sold pork before delisting
- Achieve animation effect through event binding
- fopen、fread、fwrite、fseek 的文件处理示例
- Mysql5.7 installation tutorial graphic explanation
猜你喜欢
力扣刷题日记/day2/2022.6.24
Mysql5.7 installation tutorial graphic explanation
Scala基础教程--18--集合(二)
中国农科院基因组所汪鸿儒课题组诚邀加入
[mathematical modeling of graduate students in Jiangxi Province in 2022] analysis and code implementation of haze removal by nucleation of water vapor supersaturation
线上MySQL的自增id用尽怎么办?
Wireshark packet capturing TLS protocol bar displays version inconsistency
How is the entered query SQL statement executed?
同事悄悄告诉我,飞书通知还能这样玩
华为云ModelArts的使用教程(附详细图解)
随机推荐
【211】go 处理excel的库的详细文档
华为云ModelArts的使用教程(附详细图解)
Li Kou brush question diary /day3/2022.6.25
Achieve animation effect through event binding
Open source PostgreSQL extension age for graph database was announced as the top-level project of Apache Software Foundation
Journal des problèmes de brosse à boutons de force / day6 / 6.28
SIGMOD’22 HiEngine论文解读
Tutorial on the use of Huawei cloud modelarts (with detailed illustrations)
被忽视的问题:测试环境配置管理
78 year old professor Huake impacts the IPO, and Fengnian capital is expected to reap dozens of times the return
Scala基础教程--20--Akka
Is it safe to open an account online? is that true?
[daily question] 871 Minimum refueling times
Numpy 的仿制 2
6.26CF模拟赛B:数组缩减题解
[210] usage of PHP delimiter
Scala基础教程--15--递归
Unity makes revolving door, sliding door, cabinet door drawer, click the effect of automatic door opening and closing, and automatically play the sound effect (with editor extension code)
Li Kou brush question diary /day1/2022.6.23
未来几年中,软件测试的几大趋势是什么?