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


    🚀 优质资源分享 🚀

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

    前言

    复现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());
    });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    问题复现

    问题描述:代码使用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;
     }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

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

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

    server.tomcat.max-threads=1
    
    
    • 1
    • 2

    配置文件的加载如上,具体代码首行有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();
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    凛冬已至,望各位早日背上行囊出发
    JavaScript 中的 Range 和 Selection 对象
    使用GIt小组分工完成积分商城系统-SSM
    四、文件管理(三)文件系统
    Log4j 2.17.0 再曝漏洞,但不要惊慌
    BMZCTF phar???
    精品springboot的二手车管理系统vue
    docker安装RabbitMQ及安装延迟插件
    BM25(Best Matching 25)算法基本思想
    [附源码]Python计算机毕业设计Django校园商铺
  • 原文地址:https://blog.csdn.net/u013190417/article/details/126339144