线程重用问题–ThreadLocal数据错乱

虚幻大学 xuhss 202℃ 0评论

? 优质资源分享 ?

学习路线指引(点击解锁) 知识定位 人群定位
? Python实战微信订餐小程序 ? 进阶级 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
?Python量化交易实战? 入门级 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

前言

复现Java业务开发常见错误100例--1

项目完整代码:Github地址
1d46fc3bee371b0d0d72233e416b8029 - 线程重用问题--ThreadLocal数据错乱

知识点回顾:

ThreadLocal的定义和使用:

ThreadLocal概念以及使用场景

配置文件的读取:

获取配置文件中的key和value;

  1. 创建属性对象
  2. 获取文件流,并进行加载
  3. 遍历文件流获得属性key和value
  4. 属性赋值
Properties p=new Properties();
InputStream stream = clazz.getClassLoader().getResourceAsStream(fileName);
p.load(stream);
p.forEach((k,v)->{
    log.info("{}={}",k,v);
    System.setProperty(k.toString(),v.toString());
});

问题复现

问题描述:代码使用ThreadLocal后,有时获取的用户信息是别人的。
bb58f592013c60e67c935ff1e87489f1 - 线程重用问题--ThreadLocal数据错乱
before:是没有传递值是获取ThreadLocal中的数据;设置用户信息之前先查询一次ThreadLocal中的用户信息
after:是设置ThreadLocal中的值后输出的;设置用户信息之后再查询一次ThreadLocal中的用户信息
由第二个图可以看到before的数据本应该为null,但是现在取的是第一次塞的值1

复现过程

各位可以思考下,接下来进行复现过程:
代码思路比较简单:

  1. 创建SpringBoot项目,实现controller层
  2. 创建ThreadLocal对象
  3. 对ThreadLocal赋值前,获取线程信息和用户值
  4. 对ThreadLocal赋值
  5. 对ThreadLocal赋值后,获取线程信息和用户值
  6. 两者比较即可
  7. 启动前需要读取配置文件(注意点)

代码如下:

/**
 * @author xbhog
 * @describe:
 * @date 2022/8/10
 */

@RestController
@RequestMapping("threadlocal")
public class ThreadLocalDemo {
    private static final ThreadLocal CURRENT\_USER = new ThreadLocal();
 @GetMapping("wrong")
 public Map Wrong(@RequestParam("userId") Integer userId){
 //设置用户信息之前先查询一次ThreadLocal中的用户信息
 String before = Thread.currentThread().getName() + ":" + CURRENT\_USER.get();
 //设置ThreadLocal中的用户数据
 CURRENT\_USER.set(userId);
 //设置用户信息之后再查询一次ThreadLocal中的用户信息
 String after = Thread.currentThread().getName() + ":" + CURRENT\_USER.get();
 //汇总两次的执行结果输出
 Map result = new HashMap();
 result.put("before",before);
 result.put("after",after);
 return result;
 }
}

按理说设置用户信息之前第一次获取的值是null,但是要意识到,程序运行在Tomcat中,执行程序的线程是Tomcat的工作线程,而其工作线程是基于线程池使用的。

由上可知,线程池会使用固定的几个线程,一旦线程重用,那么很有可能会获得前一次或者其他用户请求的遗留值,这时候ThreadLocal中的用户信息就是其他用户的信息。

为了方便演示,在配置文件中设置下tomcat参数,将工作线程池最大线程数设置为1,这样始终是同一个线程在处理请求。

server.tomcat.max-threads=1

配置文件的加载如上,具体代码首行有GitHub地址,欢迎star
通过上述的分析,我们明白了出现的原因,所以只要我们在使用完后,进行删除ThreaLocal中的数据即可。
不光可以防止数据重复,也可以防止内存泄露(虽然出现的概率比较小)。
正确代码如下:

@GetMapping("right")
    public Map Rigth(@RequestParam("userId") Integer userId){
        //设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before = Thread.currentThread().getName() + ":" + CURRENT_USER.get();
        //设置ThreadLocal中的用户数据
        CURRENT_USER.set(userId);
        try{
            //设置用户信息之后再查询一次ThreadLocal中的用户信息
            String after = Thread.currentThread().getName() + ":" + CURRENT_USER.get();
            //汇总两次的执行结果输出
            Map result = new HashMap();
            result.put("before",before);
            result.put("after",after);
            return result;
        }finally {
            //删除ThreadLocal数据,既避免了内存溢出的风险也解决了数据重复的问题
            CURRENT_USER.remove();
        }
    }

转载请注明:xuhss » 线程重用问题–ThreadLocal数据错乱

喜欢 (0)

您必须 登录 才能发表评论!