通过一次线上case深入认识 ThreadLocal

前言

通过一次线上故障深入了解一下ThreadLocal, 以及相关的其他知识。

ThreadLocal的介绍

在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。 初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。 然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
什么地方用ThreadLocal呢?

  1. 记录当前线程的一些资源,把一些全局的变量存起来,当前线程使用。
  2. 分布式锁的可重入锁。

一: 匪夷所思的线上故障

项目介绍

项目主要有两个ThreadLocal, okData 和 errData, 分别存放校验通过的数据和异常的数据,主要流程,举例添加10个商品数据。

通过一次线上case深入认识 ThreadLocal

奇怪的异常信息

现在的异常是。用户只添加了一个商品,提示异常,但是实际商品列表确添加成功,非常奇怪。okData和errData里面怎么会出现都值的情况,而且在异常参数转化时报了异常。
通过一次线上case深入认识 ThreadLocal
这个异常的信息跟商品完全没关系,这个是怎么导致的呢。

排查流程。

1、一开始是任务代码逻辑异常,查看所有与app相关的信息,发现没有错误。
2、尝试复现,将出错用户的所有信息同步到线下,执行同样的操作均无法发现。
3、突然想起可能是上一个用到该线程的存储的信息。
通过一次线上case深入认识 ThreadLocal
4、果然,查看出错请求的同一个线程号的日志,发现上一个请求是get请求。未用到ThradLocal, 前二个请求是修改操作,用到errData ,且请求有问题。错误信息正好对应了app信息。

5.终于抓到了凶手,ThreadLocal 不规范使用导致的。整个项目是在每个接口执行的finally里面调用ThreadLocal 的remove()方法,释放掉数据, 虽然只是简单的几句话,但是排查起来还是很费力的。

6.后面肯定是处理了,这个应该通过aop在切面统一处理。

深入研究ThreadLocal。

首先了解一下Threadlocal的基础:
一个Thread中只有一个ThreadLocalMap,一个ThreadLocalMap中可以有多个ThreadLocal对象,其中一个ThreadLocal对象对应一个ThreadLocalMap中的一个Entry(也就是说:一个Thread可以依附有多个ThreadLocal对象)。通过源码发现ThreadLocalMap的Entry 的key就是ThreadLocal对象,value是值,其中有个地方我发现有点奇怪,为什么ThreadLocalMap使用弱引用呢。
通过一次线上case深入认识 ThreadLocal

先了解些弱引用

中所周知,JVM 进行GC的时候,会把无引用的内存对象给回收。弱引用就是如果一个内存对象只有弱引用,也会被回收,自己来测试一下。
声明一个弱引用A 和 强引用B
通过一次线上case深入认识 ThreadLocal

通过一次线上case深入认识 ThreadLocal
解释下: people内存对象只有弱引用的时候,gc会回收,导致弱引用指向null.

看看ThreadLocal里面的弱引用。

如果ThreadLocal 对象置为null,由于仅有ThreadLocalMap 中Entry 的key的弱引用。此时gc的时候, key会变成null. 由于Threadlcoal 的get()/ set() 以及table的resize()方法里均有处理,以get()为例说明 里面有
通过一次线上case深入认识 ThreadLocal
通过一次线上case深入认识 ThreadLocal
通过一次线上case深入认识 ThreadLocal
这里会针对所有key未null的脏entry进行清理。可以实战一下