美文网首页
线程重用问题--ThreadLocal数据错乱

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

作者: 欧子有话说_ | 来源:发表于2022-08-15 09:46 被阅读0次

前言

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

image.png

配置文件的读取:

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

  1. 创建属性对象
  2. 获取文件流,并进行加载
  3. 遍历文件流获得属性key和value
  4. 属性赋值
<pre class="prettyprint hljs livescript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">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());
});</pre>

问题复现

问题描述:代码使用ThreadLocal后,有时获取的用户信息是别人的。

image.png

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

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

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

复现过程

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

代码思路比较简单:

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

代码如下:

<pre class="prettyprint hljs kotlin" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">/**
 * @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;
    }
}</pre>

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

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

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

<pre class="hljs vbscript" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 0.75em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">server.tomcat.max-threads=1</pre>

配置文件的加载如上,具体代码首行有GitHub地址,欢迎star

通过上述的分析,我们明白了出现的原因,所以只要我们在使用完后,进行删除ThreaLocal中的数据即可。

不光可以防止数据重复,也可以防止内存泄露(虽然出现的概率比较小)。

正确代码如下:

<pre class="prettyprint hljs dart" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, &quot;Courier New&quot;, monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; word-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@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();
        }
    }</pre>

相关文章

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

    前言 复现Java业务开发常见错误100例--1 配置文件的读取: 获取配置文件中的key和value; 创建属性...

  • ThreadLocal 详解,超级详细

    什么是 ThreadLocal? ThreadLocal 诞生于 JDK 1.2,用于解决多线程间的数据隔离问题。...

  • Java 面试系列:ThreadLocal 有什么用 + 面试题

    什么是 ThreadLocal?ThreadLocal 诞生于 JDK 1.2,用于解决多线程间的数据隔离问题。也...

  • ThreadLocal

    ThreadLocal是为了解决线程间数据共享带来的问题 看下Thread, ThreadLocal和Thread...

  • 1-50

    1threadlocal使用时注意的问题 线程池使用ThreadLocal, 会获取到上个用户的数据. 退...

  • java基础19-常见坑

    1.使用threadlocal缓存信息,使用之后没有remove,引起数据错乱。因为threadlocal缓存的数...

  • ThreadLocal理解

    一、ThreadLocal的作用 ThreadLocal以线程作为作用域,针对每个线程维护他们自己的数据。每个线程...

  • iOS 多线程技术有些啥,如何解决多线程带来的隐患

    有多线程就有因为多线程造成的数据安全问题(如何确保同一块共享内存在多线程下不发生数据错乱和数据安全问题) 线程同步...

  • ThreadLocal的作用和实现原理

    ThreadLocal的作用 ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,...

  • ThreadLocal介绍

    ThreadLocal简介 ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数...

网友评论

      本文标题:线程重用问题--ThreadLocal数据错乱

      本文链接:https://www.haomeiwen.com/subject/tivkgrtx.html