不规范使用ThreadLocal导致bug分析解决
作者:程序员拾山 发布时间:2023-11-24 20:03:59
因为线程重用导致的信息错乱的bug
ThreadLocal一般用于线程间的数据隔离,通过将数据缓存在ThreadLocal中,可以极大的提升性能。但是,如果错误的使用Threadlocal,可能会引起不可预期的bug,以及造成内存泄露。
有时我们会在一个接口中缓存某些数据到ThreadLocal中,但是我们要意识到,处理请求的这些线程是由tomcat提供的,而tomcat提供的线程都是配置在一个线程池中的。
也就是说,线程是可能被重用的,如果线程一旦被重用,而ThreadLocal的数据没有及时重置,就会导致数据被混乱使用。
以下方的接口为例,先获取当前线程中保存的数据信息,将参数中的name保存到ThreadLocal中以后,再获取一次。
@GetMapping(value = "/threadLocal")
public ResponseEntity<Object> threadLocal(String name) {
String before = Thread.currentThread().getName() + ":" + threadLocal.get();
//先获取值,理论上应该是null
System.out.println("before:" + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + ":" + threadLocal.get();
//设置完参数值再获取一次
System.out.println("after:" + after);
return ResponseEntity.ok().build();
}
为了尽快复现线程重用导致的问题,我们将servlet.tomcat.threads.max设置为1,这样每次请求使用的都是同一个线程。
第一次请求接口,数据看起来很正常:
但是第二次请求接口时,可以看到线程仍然是http-nio-8080-exec-1,但是before却打印出了第一次请求的参数test。
这就是因为没有及时重置ThreadLocal导致的数据错误。
正确使用的姿势
修正的办法就是处理完接口之后要及时清理ThreadLocal。
@GetMapping(value = "/threadLocal")
public ResponseEntity<Object> threadLocal(String name) {
try {
String before = Thread.currentThread().getName() + ":" + threadLocal.get();
//先获取值,理论上应该是null
System.out.println("before:" + before);
threadLocal.set(name);
String after = Thread.currentThread().getName() + ":" + threadLocal.get();
//设置完参数值再获取一次
System.out.println("after:" + after);
} finally {
//清理数据
threadLocal.remove();
}
return ResponseEntity.ok().build();
}
更优雅的处理方式
可能也有的朋友会说,每次都要使用try finally处理线程数据,未免也太麻烦了。其实,我们可以使用 * 或者过滤器自动帮我们完成数据的初始化以及清理工作。
最后
我们在写业务代码时,正确的理解线程的全生命周期以及执行原理,对我们提升代码的健壮性其实很有帮助。有时我们觉得底层原理很枯燥乏味,开发业务就是写增删改查,多线程用的也很少,但我们只是没有意识到,我们的代码一直跑在tomcat提供的线程池中,本身就是一个多线程的环境。
除了tomcat的线程池,我们自定义的线程池其实也会有这个问题,大家可以看看自己的业务代码是否踩过这个坑。
来源:https://juejin.cn/post/7186122423040180281


猜你喜欢
- 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。深刻认识Stri
- 本文实例为大家分享了使用PageHelper插件实现Service层分页的具体代码,供大家参考,具体内容如下使用场景:平时分页我们可以直接使
- 基本布局演示1. 定义包含GridView 的 main.xmk<?xml version="1.0" encod
- 1. 前言Spring的核心技术IOC(Intorol of Converse控制反转)的实现途径是DI(dependency Insert
- 前言前几天有个需求,需要使用不同的数据源,例如某业务要用A数据源,另一个业务要用B数据源。我上网收集了一些资料整合了一下,虽然最后这个需求不
- java事件机制中包含下述三要素:1、事件,发生了什么事,比如用户在界面上的一个操作(手势滑动屏幕),当一个事件发生的时候,该事件用一个事件
- Apache 和 Tomcat 都是web网络服务器,两者既有联系又有区别,在进行HTML、PHP、JSP、P
- 本文介绍了最好的Java5种遍历HashMap数据的写法,分享给大家,也给自己留一个笔记,具体如下:通过EntrySet的迭代器遍历Iter
- 1:在 Visual Studio Code 中打开扩展视图(Ctrl+Shift+X),输入关键词java、spring分别下载Java开
- 传输层安全性协议(英语:Transport Layer Security,缩写作 TLS),及其前身安全套接层(Secure Sockets
- 利用栈实现一个简易计算器(Java实现),供大家参考,具体内容如下一、思路分析当我们输入一个类似于“7*2+100-5+
- Android开发sdk过程中,很有可能在sdk内部引
- 前几天,收到 AS 发布的 3.0 更新,就迫不及待的更新了,更新后发现整个界面的画风都变了,和 IDEA 更像了本人是命令行重度使用患者,
- 之前有简单介绍过java多线程的使用,已经Thread类和Runnable类,为了更好地理解多线程,本文就Thread进行详细的分析。sta
- 网上找了几个,写的都不太适合,有的写出来了,也没有给出参考的算法链接。这样就导致了如果产生错误我们无法排查(不懂原理怎么排查对吧)。如果在使
- 本文实例讲述了C#模拟window操作鼠标的方法。分享给大家供大家参考。具体实现方法如下:using System;using System
- 需求读200+的CSV/EXCEL文件,按文件名称存到不同数据库前期准备环境maven + jdk8 + mysql代码展示pom文件<
- 前言加密配置是一个很常见的需求,在spring boot生态中,已经有非常多的第三方starter实现了,博主所在公司也有这种强制要求,一些
- 1、通过WindowManager获取DisplayMetrics dm = new DisplayMetrics();getWindowM
- 在文件夹中,我们经常有类似s_1.txt、s_2.txt、s_10.txt、s_11.txt这样的命名方式,我们期望的排序方式是s_1.tx