在系统开发过程当中常使用ThreadLocal进行传递日志的RequestId,由此来获取整条请求链路。然而当线程中开启了其余的线程,此时ThreadLocal里面的数据将会出现没法获取/读取错乱,甚至还可能会存在内存泄漏等问题,下面用代码来演示一下这个问题。html
普通代码示例:java
并行流代码示例:git
ThreadLocal的子类InheritableThreadLocal其实已经帮咱们处理好了,经过这个组件能够实现父子线程之间的数据传递,在子线程中可以父线程中的ThreadLocal本地变量。github
能够看出InheritableThreadLocal继承自ThreadLocal,并重写了三个相关方法。api
再回来过来看ThreadLocal的源码:缓存
咱们发现InheritableThreadLocal中createMap,以及getMap方法处理的对象不同了,其中在ThreadLocal中处理的是threadLocals,而InheritableThreadLocal中的是inheritableThreadLocals,咱们再顺藤摸瓜看一下Thread对象的处理,其中在init源码中咱们看到这么一段代码:oracle
代码的意思是在Thread获取先父亲线程parent(即要建立子线程的当前这个线程)。当父亲线程中对inherThreadLocals进行了赋值,就会把当前线程的本地变量(也就是父线程的inherThreadLocals)进行createInheritedMap方法操做。查看源码createInheritedMap方法,源码可知此操做就是将赋线程的threadLocalMap传递给子线程。框架
咱们写个代码测试一下:分布式
看起来彷佛真的是解决了咱们没法传递的问题。测试
测试结果显示两次赋值,获得的结果仍是第一次的值!为何?
其实缘由也很简单,咱们的线程池会缓存使用过的线程。当线程须要被重复利用的时候,并不会再从新执行init()初始化方法,而是直接使用已经建立过的线程,因此这里的值不会二次产生变化,那么该怎么作到真正的父子线程数据传递呢?
JDK的InheritableThreadLocal类能够完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的状况,线程由线程池建立好,而且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用须要的其实是把任务提交给线程池时的ThreadLocal值传递到任务执行时。
首先分析一下最核心的类:TransmittableThreadLocal
首先TransmittableThreadLocal继承自InheritableThreadLocal,这样能够在不破坏原有InheritableThreadLocal特性的状况下,还能充分使用Thread线程建立过程当中执行init方法,从而达到父子线程传递数据的目的。
这里有一个很重要的变量holder:源码以下
1. holder中存放的是InheritableThreadLocal本地变量。
2. WeakHashMap支持存放空置。
主要的几个相关方法:源码以下
1. get方法调用时,先获取父亲的相关数据判断是否有数据,而后在holder中把自身也给加进去。
2. set方法调用时,先在父亲中设置,再本地判断是holder否为删除或者是新增数据。
3. remove调用时,先删除自身,再删除父亲中的数据,删除也是直接以自身this做为变量Key。
采用包装的形式来处理线程池中的线程不会执行初始化的问题,源码以下:
1. 先取得holder。
2. 备份线程本地数据
3. run原先的方法
4. 还原线程本地数据
备份方法:
1. 先获取holder中的数据
2. 进行迭代,数据在captured中不存在,可是holder中存在,说明是后来加进去的,进行删除。
3. 再将captured设置到当前线程中。
还原方法:
1. 先获取holder中的数据
2. backup中不存在,holder中存在,说明是后面加进去的,进行删除还原操做。
3. 再将backup设置到当前线程中。
分布式跟踪系统
日志收集记录系统上下文
应用容器或上层框架跨应用代码给下层SDK传递信息