前言

复现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());
});

问题复现

问题描述:代码使用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<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数据错乱的更多相关文章

  1. 详解Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失

    在Spring Cloud中我们用Hystrix来实现断路器,Zuul中默认是用信号量(Hystrix默认是线程)来进行隔离的,我们可以通过配置使用线程方式隔离. 在使用线程隔离的时候,有个问题是必须 ...

  2. Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

    1.线程范围内共享变量 1.1 前奏: 使用一个Map来实现线程范围内共享变量 public class ThreadScopeShareData { static Map<Thread, In ...

  3. 看看线程特有对象ThreadLocal

    作用:设计线程安全的一种技术. 在使用多线程的时候,如果多个线程要共享一个非线程安全的对象,常用的手段是借助锁来实现线程的安全.线程安全隐患的前提是多线程共享一个不安全的对象 ,那么有没有办法让线程之 ...

  4. 线程本地存储 ThreadLocal

    线程本地存储 · 语雀 (yuque.com) 线程本地存储提供了线程内存储变量的能力,这些变量是线程私有的. 线程本地存储一般用在跨类.跨方法的传递一些值. 线程本地存储也是解决特定场景下线程安全问 ...

  5. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  6. Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic

    Atitit usrqbg1821 Tls 线程本地存储(ThreadLocal Storage 规范标准化草案解决方案ThreadStatic 1.1. ThreadLocal 设计模式1 1.2. ...

  7. 线程本地变量ThreadLocal源码解读

      一.ThreadLocal基础知识 原始线程现状: 按照传统经验,如果某个对象是非线程安全的,在多线程环境下,对对象的访问必须采用synchronized进行线程同步.但是Spring中的各种模板 ...

  8. 线程本地变量ThreadLocal

    一.本地线程变量使用场景 并发应用的一个关键地方就是共享数据.如果你创建一个类对象,实现Runnable接口,然后多个Thread对象使用同样的Runnable对象,全部的线程都共享同样的属性.这意味 ...

  9. 线程池与Threadlocal

    线程池与Threadlocal 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...

随机推荐

  1. 模块re正则

    正则表达式 内容概要 正则表达式前戏 正则表达式之字符组 正则表达式特殊符号 正则表达式量词 正则表达式贪婪与非贪婪匹配 正则表达式取消转义 python内置模块之re模块 内容详情 正则表达式前戏 ...

  2. GDKOI 2021 Day1 PJ 爆炸记

    早上睡到 7:10 分才想起今天有 GDKOI ,赶紧去买了一个面包赶去机房 发现隔壁的大奆都过来了.比赛时由于昨晚一直没睡好,打了两个小时的哈欠 T1 :暴力模拟 根据 \(r\) 和 \(c\) ...

  3. DS18B20数字温度计 (一) 电气特性, 供电和接线方式

    目录 DS18B20数字温度计 (一) 电气特性, 供电和接线方式 DS18B20数字温度计 (二) 测温, ROM和CRC校验 DS18B20数字温度计 (三) 1-WIRE总线ROM搜索算法 DS ...

  4. 【视频】k8s套娃开发调试dapr应用 - 在6月11日【开源云原生开发者日】上的演示

    这篇博客是在2022年6月11日的[开源云原生]大会上的演讲中的演示部分.k8s集群套娃(嵌套)是指在一个k8s的pod中运行另外一个k8s集群,这想法看上去很疯狂,实际上非常实用. k8s集群套娃( ...

  5. SpringBoot之:SpringBoot的HATEOAS基础

    目录 简介 链接Links URI templates Link relations Representation models 总结 简介 SpringBoot提供了HATEOAS的便捷使用方式,前 ...

  6. Spring Boot 实践 :Spring Boot + MyBatis

    Spring Boot 实践系列,Spring Boot + MyBatis . 目的 将 MyBatis 与 Spring Boot 应用程序一起使用来访问数据库. 本次使用的Library spr ...

  7. Vue MD5加密你用吗?

    安装 npm install --save js-md5 1.按需引入(在你需要的项目中引入) 引入: import md5 from 'js-md5' 使用: md5('加密信息') 2.全局引入( ...

  8. SAP 实例 13 Random Grouping with LOOP

    REPORT demo_loop_group_by_random. CLASS demo DEFINITION. PUBLIC SECTION. CLASS-METHODS: main, class_ ...

  9. 程序员必备,一款让你提高工作效率N倍的神器uTools

    下载地址:https://www.aliyundrive.com/s/f7PU7QxdxEz uTools 是什么? uTools = your tools(你的工具集) uTools 是一个极简.插 ...

  10. 深入解析kubernetes controller-runtime

    Overview controller-runtime 是 Kubernetes 社区提供可供快速搭建一套 实现了controller 功能的工具,无需自行实现Controller的功能了:在 Kub ...