ThreadLocal在tomcat中运行的并发问题
我们知道,ThreadLocal 适用于变量在线程间隔离,而在方法或类间共享的场景。如果用户信息的获取比较昂贵(比如从数据库查询用户信息),那么在 ThreadLocal 中缓存数据是比较合适的做法。
问题描述:
使用 Spring Boot 创建一个 Web 应用程序,使用 ThreadLocal 存放用户信息,这个key值固定为常量。
使用ThreadLocal的业务逻辑
模拟多并发的情况下
实现通过前端页面完成两个用户登录,这两个用户的登录后,用户信息都将保存在Redis中。然后找到业务逻辑代码,不断刷新页面,模拟高并发情况。
- 拦截器中部分业务代码
//2.判断token登录的用户是否存在
JSONObject jsonObject = JSONUtil.toBean((String) redisService.get(SystemConstant.PROJECT_NAME + RedisConstant.LOGIN_TOKEN + token), JSONObject.class);
StudentInfo studentInfo = JSONUtil.toBean(jsonObject, StudentInfo.class);
if (studentInfo != null) {
//刷新时间
redisService.expires(SystemConstant.PROJECT_NAME + RedisConstant.LOGIN_TOKEN + token, RedisConstant.LOGIN_TOKEN_TTL, TimeUnit.MINUTES);
} else {
logger.debug("token过期");
response.getWriter().write(JSONUtil.toJsonStr(ResultWrapper.failure("未认证")));
return false;
}
//绑定线程用户对象
if (ThreadLocalUtil.get(SystemConstant.USER_INFO) == null) {
ThreadLocalUtil.set(SystemConstant.USER_INFO, studentInfo);
}
System.out.println(token);
System.out.println(ThreadLocalUtil.get(SystemConstant.USER_INFO).toString());
在不断刷新过程中(在并发场景下),threadLocal 会出现诡异的赋值,为了更快复现问题,我们将tomcat 工作线程设置为默认(多个固定线程)
第一次刷新后,ThreadLocal绑定的数据在控制台打印
多次次刷新后,ThreadLocal绑定的数据在控制台打印
可以看出,同样的token但是绑定的数据不同,也就是说是Redis查询的数据一致,ThreadLocal中绑定的数据造成混乱,也可以说是并发情况下,ThreadLocal绑定的数据不是那么可靠。
问题导致原因
在经过多次debug,发现数据代码没有问题,因为Redis在同样的key情况下,数据是一样的(不考虑期间有修改的情况下)。
有一时间突然想到了tomcat是存在线程池这么一回事的,也就是说线程池一个特性就是线程的重复使用,会不会是tomcat线程复用导致threadlocal数据是上一次该线程绑定的呢?再仔细想一想,高并发下就可能导致线程多次使用。
当使用一个线程时,发现就不会出现上述问题了。
解决方法
使用类似 ThreadLocal 工具来存放一些数据时,需要特别注意在代码运行完后,显式地去清空设置的数据。如果在代码中使用了自定义的线程池,也同样会遇到这个问题。这里我是使用的拦截器嘛,拦截器有一个PostHandler方法,实在业务代码执行后,响应前调用,所以我可以在里面进行Threallocal解除绑定的数据
修正后的代码如下:
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
ThreadLocalUtil.remove(SystemConstant.USER_INFO);
}
\