线程重用问题--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 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...
随机推荐
- Unity-UGUI-无限循环列表
前言:项目准备新增一个竞技场排行榜,策划规定只显示0-400名的玩家.我一想,生成四百个游戏物体,怕不是得把手机给卡死?回想原来在GitHub上看到过一个实现思路就是无限循环列表,所以就想自己试试.公 ...
- jeecgboot-vue3笔记(八)——treeSelect树形选择组件的使用(一次性加载)
使用效果 前端代码 定义interface export interface TreeDataItem { value: string; key: string; title?: string; sl ...
- FastDFS 技术整理
1.FastDFS 1.1.了解基础概念 1.1.1.什么是分布式文件系统? 全称:Distributed File System,即简称的DFS 这个东西可以是一个软件,也可以说是服务器,和tomc ...
- 利用Github Action实现Tornadofx/JavaFx打包
原文地址: 利用Github Action实现Tornadofx/JavaFx打包 - Stars-One的杂货小窝 最近开了个新项目,主要是个工具软件,也算个人的自娱自乐吧,也算开源的一部分,想着都 ...
- 【clickhouse专栏】新建库角色用户初始化
一.创建新的database clickhouse创建数据库的语法几乎和其他的关系型数据库是一样的,区别就是clickhouse存在集群cluster和库引擎engine的概念,可以根据需要进行指定. ...
- Linux系统安装ActiveMQ
下载安装包 https://activemq.apache.org/components/classic/download/ 上传至服务器并解压 [root@localhost activemq]# ...
- ShardingSphere-proxy-5.0.0部署之分表实现(一)
一.说明 环境准备:JDK8+ mysql 5.x 官网:https://shardingsphere.apache.org/ 下载地址:https://archive.apache.org/ ...
- 在C#中使用正则表达式最简单的方式
更新记录 本文迁移自Panda666原博客,原发布时间:2021年5月11日. 在.NET中使用正则表达式与其他语言并无太大差异.最简单的使用就是使用Regex类型自带的静态方法. 注意:在.NET中 ...
- 根据数据中的key获取value值
一.测试数据准备 List<Map<String, String>> result = new ArrayList();Map<String, String> ma ...
- BUUCTF-qr
qr 签到题