多线程之ThreadLocal在tomcat中运行的并发问题

475 阅读2分钟

ThreadLocal在tomcat中运行的并发问题

我们知道,ThreadLocal 适用于变量在线程间隔离,而在方法或类间共享的场景。如果用户信息的获取比较昂贵(比如从数据库查询用户信息),那么在 ThreadLocal 中缓存数据是比较合适的做法。

问题描述:

使用 Spring Boot 创建一个 Web 应用程序,使用 ThreadLocal 存放用户信息,这个key值固定为常量。

使用ThreadLocal的业务逻辑

image-20220723215906851

模拟多并发的情况下

实现通过前端页面完成两个用户登录,这两个用户的登录后,用户信息都将保存在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绑定的数据在控制台打印

image-20220723220908940

多次次刷新后,ThreadLocal绑定的数据在控制台打印

image-20220723221142978

可以看出,同样的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);
    }
​

\