不规范使用ThreadLocal导致的bug,说多了都是泪

简介: ThreadLocal一般用于线程间的数据隔离,通过将数据缓存在ThreadLocal中,可以极大的提升性能。但是,如果错误的使用Threadlocal,可能会引起不可预期的bug,以及造成内存泄露。

ThreadLocal一般用于线程间的数据隔离,通过将数据缓存在ThreadLocal中,可以极大的提升性能。但是,如果错误的使用Threadlocal,可能会引起不可预期的bug,以及造成内存泄露。
因为线程重用导致的信息错乱的bug
有时我们会在一个接口中缓存某些数据到ThreadLocal中,但是我们要意识到,处理请求的这些线程是由tomcat提供的,而tomcat提供的线程都是配置在一个线程池中的。
也就是说,线程是可能被重用的,如果线程一旦被重用,而ThreadLocal的数据没有及时重置,就会导致数据被混乱使用。
以下方的接口为例,先获取当前线程中保存的数据信息,将参数中的name保存到ThreadLocal中以后,再获取一次。
0.0.png

@GetMapping(value = "/threadLocal")
public ResponseEntity 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,这样每次请求使用的都是同一个线程。
00.png

第一次请求接口,数据看起来很正常:
02.png

但是第二次请求接口时,可以看到线程仍然是http-nio-8080-exec-1,但是before却打印出了第一次请求的参数test。

这就是因为没有及时重置ThreadLocal导致的数据错误。
正确使用的姿势
修正的办法就是处理完接口之后要及时清理ThreadLocal。
@GetMapping(value = "/threadLocal")
public ResponseEntity 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处理线程数据,未免也太麻烦了。其实,我们可以使用拦截器或者过滤器自动帮我们完成数据的初始化以及清理工作。
03.png

最后
我们在写业务代码时,正确的理解线程的全生命周期以及执行原理,对我们提升代码的健壮性其实很有帮助。有时我们觉得底层原理很枯燥乏味,开发业务就是写增删改查,多线程用的也很少,但我们只是没有意识到,我们的代码一直跑在tomcat提供的线程池中,本身就是一个多线程的环境。
除了tomcat的线程池,我们自定义的线程池其实也会有这个问题,大家可以看看自己的业务代码是否踩过这个坑。

相关文章
|
6月前
codereview开发问题之CodeReview中如何判断注释问题如何解决
codereview开发问题之CodeReview中如何判断注释问题如何解决
|
7月前
|
Java 数据库连接
惊呆了!JAVA反射:你的代码竟然能这样“自我修复”?
【6月更文挑战第30天】Java反射允许运行时访问类和方法,模拟“自我修复”能力。当UserService的getUserById方法抛出异常时,通过反射捕获异常并调用handleException进行处理。此示例展示了如何记录错误,返回默认用户对象,而无需原始代码更改。反射提供了一种动态异常处理机制,增强代码的适应性和弹性。
53 0
|
存储 机器学习/深度学习 缓存
我惊了!!!ThreadLocal 源码存在内存泄露的 Bug!!!
我惊了!!!ThreadLocal 源码存在内存泄露的 Bug!!!
187 0
写了个全局变量的bug,被同事们啪啪打脸
前阵子写了一个功能,测试 0 bug 就上线了,上线后也运行好好的,好多天都没有人反馈bug,超爽。。 不出问题还好,出问题就是大问题。。
|
设计模式 XML JavaScript
写了这么久代码你了解Java面向对象的设计原则吗?(一)
写了这么久代码你了解Java面向对象的设计原则吗?
132 0
写了这么久代码你了解Java面向对象的设计原则吗?(一)
|
存储 XML Java
写了这么久代码你了解Java面向对象的设计原则吗?(二)
写了这么久代码你了解Java面向对象的设计原则吗
224 0
写了这么久代码你了解Java面向对象的设计原则吗?(二)
|
消息中间件 JavaScript 小程序
发现一个Spring事务的巨坑bug,可是官方都不承认?大家来评评理!
发现一个Spring事务的巨坑bug,可是官方都不承认?大家来评评理!
|
消息中间件 JavaScript 小程序
用1个月重构了同事写的烂代码,我总结出了15条重写烂代码的经验!
用1个月重构了同事写的烂代码,我总结出了15条重写烂代码的经验!
|
存储 Java 应用服务中间件
细数 ThreadLocal 三大坑,内存泄露仅是小儿科
我在参加Code Review的时候不止一次听到有同学说:我写的这个上下文工具没问题,在线上跑了好久了。其实这种想法是有问题
细数 ThreadLocal 三大坑,内存泄露仅是小儿科