ThreadLocal父子线程之间的数据传递问题

一、问题的提出

在系统开发过程中常使用ThreadLocal进行传递日志的RequestId,由此来获取整条请求链路。然而当线程中开启了其他的线程,此时ThreadLocal里面的数据将会出现无法获取/读取错乱,甚至还可能会存在内存泄漏等问题,下面用代码来演示一下这个问题。

普通代码示例:

image

并行流代码示例:

[图片上传中...(image-b7fe28-1545751320761-12)]

二、问题的解决

ThreadLocal的子类InheritableThreadLocal其实已经帮我们处理好了,通过这个组件可以实现父子线程之间的数据传递,在子线程中能够父线程中的ThreadLocal本地变量。

三、源码的分析

image

可以看出InheritableThreadLocal继承自ThreadLocal,并重写了三个相关方法。

再回来过来看ThreadLocal的源码:

image

我们发现InheritableThreadLocal中createMap,以及getMap方法处理的对象不一样了,其中在ThreadLocal中处理的是threadLocals,而InheritableThreadLocal中的是inheritableThreadLocals,我们再顺藤摸瓜看一下Thread对象的处理,其中在init源码中我们看到这么一段代码:

image

代码的意思是在Thread获取先父亲线程parent(即要创建子线程的当前这个线程)。当父亲线程中对inherThreadLocals进行了赋值,就会把当前线程的本地变量(也就是父线程的inherThreadLocals)进行createInheritedMap方法操作。查看源码createInheritedMap方法,源码可知此操作就是将赋线程的threadLocalMap传递给子线程。

image

我们写个代码测试一下:

image

看起来似乎真的是解决了我们无法传递的问题。

四、真的就这么美好么?我们来和线程池搭配一下

image

测试结果显示两次赋值,得到的结果还是第一次的值!为什么?

其实原因也很简单,我们的线程池会缓存使用过的线程。当线程需要被重复利用的时候,并不会再重新执行init()初始化方法,而是直接使用已经创建过的线程,所以这里的值不会二次产生变化,那么该怎么做到真正的父子线程数据传递呢?

五、真正的解决方案:阿里的transmittable-thread-local了解一下

JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的ThreadLocal值传递到任务执行时。

首先分析一下最核心的类:TransmittableThreadLocal

image

首先TransmittableThreadLocal继承自InheritableThreadLocal,这样可以在不破坏原有InheritableThreadLocal特性的情况下,还能充分使用Thread线程创建过程中执行init方法,从而达到父子线程传递数据的目的。

这里有一个很重要的变量holder:源码如下

1. holder中存放的是InheritableThreadLocal本地变量。

2. WeakHashMap支持存放空置。

image

主要的几个相关方法:源码如下

1. get方法调用时,先获取父亲的相关数据判断是否有数据,然后在holder中把自身也给加进去。

2. set方法调用时,先在父亲中设置,再本地判断是holder否为删除或者是新增数据。

3. remove调用时,先删除自身,再删除父亲中的数据,删除也是直接以自身this作为变量Key。

image

采用包装的形式来处理线程池中的线程不会执行初始化的问题,源码如下:

1. 先取得holder。

2. 备份线程本地数据

3. run原先的方法

4. 还原线程本地数据

image

备份方法:

1. 先获取holder中的数据

2. 进行迭代,数据在captured中不存在,但是holder中存在,说明是后来加进去的,进行删除。

3. 再将captured设置到当前线程中。

image

还原方法:

1. 先获取holder中的数据

2. backup中不存在,holder中存在,说明是后面加进去的,进行删除还原操作。

3. 再将backup设置到当前线程中。

image

下面是几个典型场景例子。

1. 分布式跟踪系统

2. 日志收集记录系统上下文

3. 应用容器或上层框架跨应用代码给下层SDK传递信息

项目地址:https://github.com/alibaba/transmittable-thread-local

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、简介 并发编程中,当访问共享数据时,通常需要使用同步技术。但如果数据不发布(逸出)到线程以外,仅仅在单线程中被...
    邱simple阅读 8,735评论 3 12
  • 本篇文章的主要内容如下: 1、Java中的ThreadLocal2、 Android中的ThreadLocal3、...
    Sophia_dd35阅读 3,741评论 0 5
  • 本章写了作者拉力加多,到达了马尔多纳达,由于没有可以搭乘的船只,作者到格拉布杜德里布做了短暂的停留,并受到了当地行...
    吉吉简书阅读 1,326评论 0 0
  • 梁祝歌词 (女)碧草青青花盛开彩蝶双双久徘徊千古传颂生生爱山伯永恋祝英台 (男)同窗共读整三载促膝并肩两无猜十八相...
    乐度音乐阅读 5,776评论 0 0
  • 小时候因为贪玩告诉家长作业早就做完了,到学校又告诉老师,作业忘在家里了。当他们都相信你的时候,你心里得意忘形,但是...
    遗落的一洛阅读 1,579评论 0 2