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

2022-10-23,,,,

前言

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

项目完整代码:Github地址

知识点回顾:

ThreadLocal的定义和使用:

ThreadLocal概念以及使用场景

配置文件的读取:

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

    创建属性对象
    获取文件流,并进行加载
    遍历文件流获得属性key和value
    属性赋值
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后,有时获取的用户信息是别人的。

before:是没有传递值是获取ThreadLocal中的数据设置用户信息之前先查询一次ThreadLocal中的用户信息

after:是设置ThreadLocal中的值后输出的;设置用户信息之后再查询一次ThreadLocal中的用户信息

由第二个图可以看到before的数据本应该为null,但是现在取的是第一次塞的值1

复现过程

各位可以思考下,接下来进行复现过程:

代码思路比较简单:

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

代码如下:

/**
* @author xbhog
* @describe:
* @date 2022/8/10
*/ @RestController
@RequestMapping("threadlocal")
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> CURRENT_USER = new ThreadLocal<Integer>();
@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();
}
}

线程重用问题--ThreadLocal数据错乱的相关教程结束。

《线程重用问题--ThreadLocal数据错乱.doc》

下载本文的Word格式文档,以方便收藏与打印。