线程重用问题--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 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...
随机推荐
- 看Spring源码不得不会的@Enable模块驱动实现原理讲解
这篇文章我想和你聊一聊 spring的@Enable模块驱动的实现原理. 在我们平时使用spring的过程中,如果想要加个定时任务的功能,那么就需要加注解@EnableScheduling,如果想使用 ...
- 配置中心的设计-nacos vs apollo
简介 前面我们分析了携程的 apollo(见 详解apollo的设计与使用),现在再来看看阿里的 nacos. 和 apollo 一样,nacos 也是一款配置中心,同样可以实现配置的集中管理.分环境 ...
- android系统中有哪些日志
日志目录 android系统中还有很多常用的日志目录.我们可以通过adb命令把这些日志信息提取出来. data/system/dropbox data/system/usagestats data/s ...
- markdowm使用学习
markdowm学习 标题(#/##/###/####) 三级标题 四级标题 字体(*/) hello world! hello world! hello world! hello world! he ...
- 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具
上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...
- Camunda如何配置和使用mysql数据库
Camunda默认使用已预先配置好的H2数据库,数据库模式和所有必需的表将在引擎第一次启动时自动创建.如果你想使用自定义独立数据库,比如mysql,请遵循以下步骤: 一.新建mysql数据库 为Cam ...
- 教你如何用网页开发APP
用到的工具: HBuilderX app开发版1.首先你得网站必须是上线的,然后明确这一点后,点击打开HBuilderX.在文件里找到新建项目,选择wap2App,将下面信息填写完整,然后创建. 2. ...
- Redis系列2:数据持久化提高可用性
1 介绍 从上一篇的 <深刻理解高性能Redis的本质> 中可以知道, 我们经常在数据库层上加一层缓存(如Redis),来保证数据的访问效率. 这样性能确实也有了大幅度的提升,但是本身Re ...
- js 表面学习 - 认识结构2
单行注释以 // 开头. 多行注释以 /* 开头,以 */ 结尾. 任何位于 /* 和 */ 之间的文本都会被 JavaScript 忽略. JavaScript 数据类型 JavaScript 变量 ...
- Vue的基础语法
前言 Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是, Vue 被设计为可以自底向上逐层应用.Vue 的核心库只关注视图层,不仅易于上手, ...