线程重用问题

前言

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

项目完整代码:Github地址

知识点回顾:

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后,有时获取的用户信息是别人的。

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<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();         }     } 

商匡云商
Logo
注册新帐户
对比商品
  • 合计 (0)
对比
0
购物车