不规范使用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,这样每次请求使用的都是同一个线程。

不规范使用ThreadLocal导致bug分析解决

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

不规范使用ThreadLocal导致bug分析解决

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

不规范使用ThreadLocal导致bug分析解决

这就是因为没有及时重置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处理线程数据,未免也太麻烦了。其实,我们可以使用 * 或者过滤器自动帮我们完成数据的初始化以及清理工作。

不规范使用ThreadLocal导致bug分析解决

最后

我们在写业务代码时,正确的理解线程的全生命周期以及执行原理,对我们提升代码的健壮性其实很有帮助。有时我们觉得底层原理很枯燥乏味,开发业务就是写增删改查,多线程用的也很少,但我们只是没有意识到,我们的代码一直跑在tomcat提供的线程池中,本身就是一个多线程的环境。

除了tomcat的线程池,我们自定义的线程池其实也会有这个问题,大家可以看看自己的业务代码是否踩过这个坑。

来源:https://juejin.cn/post/7186122423040180281

标签:ThreadLocal,bug,解决分析
0
投稿

猜你喜欢

  • java实现打砖块小游戏

    2021-07-26 14:47:11
  • Maven如何打入依赖中指定的部分jar包

    2023-09-22 02:50:33
  • Spring Boot 文件上传与下载的示例代码

    2021-08-30 02:04:53
  • Android调用OpenCV2.4.10实现二维码区域定位

    2023-06-05 10:17:32
  • java实现Socket通信之单线程服务

    2022-08-24 14:41:06
  • Android编程之播放器MediaPlayer实现均衡器效果示例

    2022-02-25 11:52:32
  • 秒懂Java枚举类型(enum)

    2023-03-30 07:39:41
  • Java异常分类及统一处理详解

    2022-01-27 12:32:24
  • DataGridView实现点击列头升序和降序排序

    2022-07-31 21:54:01
  • 详解Java反射创建对象

    2022-12-10 03:52:23
  • Android应用中加入微信分享简单方法

    2022-06-05 00:17:27
  • Android开发实现标题随scrollview滑动变色的方法详解

    2022-10-04 07:52:24
  • Java Swing JButton按钮的实现示例

    2023-05-05 00:25:43
  • Java之Spring注解开发案例详解

    2022-05-23 05:33:02
  • Java对zip,rar,7z文件带密码解压实例详解

    2023-11-29 05:08:32
  • java Hibernate多对多映射详解及实例代码

    2023-07-02 07:24:40
  • unity 如何修改材质属性和更换shader

    2023-02-22 12:42:44
  • spring boot使用thymeleaf为模板的基本步骤介绍

    2023-12-13 15:07:23
  • Android 自定义组件卫星菜单的实现

    2023-08-16 21:29:29
  • Java生成含字母和数字的6位随机字符串

    2023-04-02 02:28:58
  • asp之家 软件编程 m.aspxhome.com