项目中的ThreadLocal内存泄漏问题

ThreadLocal概念

ThreadLocal,线程局部变量。

ThreadLocal能够存放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程安全的目的。作用就是在多线程环境下去保证成员变量的安全。

常用的方法:get()/set()/initialValue()/remove()

常用场景

  • ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的方法都可以非常方便地访问这些资源。
  • Hibernate的Session 工具类HibernateUtil
  • 通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。

原理

项目中的ThreadLocal内存泄漏问题_第1张图片
在每个线程Thread内部有一个ThreadLocalMap,这是用来存储实际的变量副本的,键值key为当前ThreadLocal变量,value为变量副本。

初始时,在Thread里面,ThreadLocalMap为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的ThreadLocalMap进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到ThreadLocalMap。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在ThreadLocalMap里面查找。 一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中的一个Entry(即一个Thread可以依附有多个ThreadLocal对象)。
[ThreadLocal原理参考]: https://blog.csdn.net/dakaniu/article/details/80829079

项目中的案例

场景:

在写公司的一个中间件项目时,需要写一个接口供外部调用。通过拦截器对外部请求进行日志记录,需要记录外部请求报文,响应报文,请求耗费时间。记录消耗时间时,考虑到多个线程之间的请求会串用局部变量,所以考虑使用ThreadLocal来包装上面的变量。

private static ThreadLocal<Date> startTime = new ThreadLocal<>();
private static ThreadLocal<Date> endTime = new ThreadLocal<>();
private static ThreadLocal<String> requestThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> responseThreadLocal = new ThreadLocal<>();
//在获取到开始时间和结束时间后,进行相减,得到耗费时间
//......

项目上生产后,发现很多请求日志的耗费时间显示为负数。进行分析后,发现是ThreadLocal出现了内存泄漏的原因,如果线程被重复使用了,就会使得之前在ThreadLocal中的变量会被重复利用,导致了不同请求的时间串用了。

解决方案:

在每个线程对ThreadLocal中的变量每一次使用完毕后,都要使用ThreadLocal的remove()方法来清除掉这个线程的变量,这样就解决了问题。

其他注意点:

ThreadLocal使用时,最好要加static修饰,避免过多创建,浪费资源。

你可能感兴趣的:(并发编程)