线程重用问题--ThreadLocal数据错乱
前言
复现Java业务开发常见错误100例--1
项目完整代码:Github地址

知识点回顾:
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数据错乱的更多相关文章
- 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失
在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须 ...
- Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类
1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...
- 看看线程特有对象ThreadLocal
作用:设计线程安全的一种技术. 在使用多线程的时候,如果多个线程要共享一个非线程安全的对象,常用的手段是借助锁来实现线程的安全.线程安全隐患的前提是多线程共享一个不安全的对象 ,那么有没有办法让线程之 ...
- 线程本地存储 ThreadLocal
线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...
- Android线程管理之ThreadLocal理解及应用场景
前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...
- Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic
Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...
- 线程本地变量ThreadLocal源码解读
一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...
- 线程本地变量ThreadLocal
一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...
- 线程池与Threadlocal
线程池与Threadlocal 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...
随机推荐
- 论文阅读 Dynamic Network Embedding by Modeling Triadic Closure Process
3 Dynamic Network Embedding by Modeling Triadic Closure Process link:https://scholar.google.com.sg/s ...
- 优先队列STL
引入 优先队列是一种特殊的队列,它的功能是--自动排序. 基本操作: q.size(); //返回q里元素个数 q.empty(); //返回q是否为空,空则返回1,否则返回0 q.push(k); ...
- Eclipse for Python开发环境部署
Eclipse for Python开发环境部署 工欲善其事,必先利其器. 对开发人员来说,顺手的开发工具必定事半功倍.自学编程的小白不知道该选择那个开发工具,Eclipse作为一个功能强大且开源免费 ...
- 关于Node.js 链接mysql超时处理(默认8小时)
备注:这是在pm2配置node环境下,超过8小时mysql自动关闭的情况下出现的解决方法:1.封装mysql.js var mysql = require('mysql'); var connecti ...
- 2分钟实现一个Vue实时直播系统
前言 我们在不敲代码的时候可能会去看游戏直播,那么是前台怎么实现的呢?下面我们来讲一下.第一步,购买云直播服务 首先,你必须去阿里云或者腾讯云注册一个直播服务.也花不了几个钱,练手的话,几十块钱就够了 ...
- SAP 实例 12 List Box with Value List from PBO Module
REPORT demo_dynpro_dropdown_listbox. DATA: name TYPE vrm_id, list TYPE vrm_values, value LIKE LINE O ...
- .NET6接入Skywalking链路追踪完整流程
一.Skywalking介绍 Skywalking是一款分布式链路追踪组件,什么是链路追踪? 随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务.互联网应用构建在不同的软 ...
- RPA应用场景-账套建立
所涉人工数量5操作频率 不定时 场景流程 1.客户按照项目开设专项财务管理,每个项目需要在初期建立自己的账套: 2.运营专员通过邮件发送账套建立申请: 3.根据申请进入金蝶运维后台,依据规则完成账套建 ...
- runc hang 导致 Kubernetes 节点 NotReady
Kubernetes 1.19.3 OS: CentOS 7.9.2009 Kernel: 5.4.94-1.el7.elrepo.x86_64 Docker: 20.10.6 先说结论,runc v ...
- VScode中配置Java环境
vscode 中配置Java环境 转载说明:本篇文档原作者[@火星动力猿],文档出处来自哔哩哔哩-[教程]VScode中配置Java运行环境 转载请在开头或显眼位置标注转载信息. 1.下载VScode ...