一、问题的提出

在系统开发过程中常使用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

作者:面瘫男的自我修养
链接:https://www.jianshu.com/p/8fd635f50016
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

解决threadLocal父子变量传递问题的更多相关文章

  1. ThreadLocal父子线程传递实现方案

    介绍InheritableThreadLocal之前,假设对 ThreadLocal 已经有了一定的理解,比如基本概念,原理,如果没有,可以参考:ThreadLocal源码分析解密.在讲解之前我们先列 ...

  2. 使用 transmittable-thread-local 组件解决 ThreadLocal 父子线程数据传递问题

    在某个项目中,需要使用mybatis-plus多租户功能以便数据隔离,前端将租户id传到后端,后端通过拦截器将该租户id设置到ThreadLocal以便后续使用,代码大体上如下所示: ThreadLo ...

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

    一.问题的提出 在系统开发过程中常使用ThreadLocal进行传递日志的RequestId,由此来获取整条请求链路.然而当线程中开启了其他的线程,此时ThreadLocal里面的数据将会出现无法获取 ...

  4. vue-cli中父子组件间的变量传递

    vue-cli中父子组件间的变量传递 在vue中每一个组件的作用域都是独立的,如果我们想实现父子组件间变量的传递就要另寻他法,而不能直接调用其中的变量. 父级组件向子级组件传递变量 要实现这种效果我们 ...

  5. ThreadLocal的坑--ThreadLocal跨线程传递问题

    1.父子线程间的传递问题 ThreadLocal的子类InheritableThreadLocal其实已经帮我们处理好了,通过这个组件可以实现父子线程之间的数据传递,在子线程中能够父线程中的Threa ...

  6. 通过transmittable-thread-local源码理解线程池线程本地变量传递的原理

    前提 最近一两个月花了很大的功夫做UCloud服务和中间件迁移到阿里云的工作,没什么空闲时间撸文.想起很早之前写过ThreadLocal的源码分析相关文章,里面提到了ThreadLocal存在一个不能 ...

  7. InheritableThreadLocal类原理简介使用 父子线程传递数据详解 多线程中篇(十八)

      上一篇文章中对ThreadLocal进行了详尽的介绍,另外还有一个类: InheritableThreadLocal 他是ThreadLocal的子类,那么这个类又有什么作用呢?   测试代码 p ...

  8. 在activity之间通过静态变量传递数据

    在activity之间通过静态变量传递数据 一.简介 主要作用:解决intent不能传递非序列化的对象 评价:简单方便,但是容易发生内存泄露,所以要及时回收内存 二.具体操作 1.在传输数据的页面弄好 ...

  9. 一张图解决ThreadLocal

    一张图解决ThreadLocal 一.前言 年底梳理知识体系时,研究了一下ThreadLocal的源码,整理了一张核心图. 想着,都走到这一步了,那就写一篇深度解读的文章吧.看过我之前文章的小伙伴都知 ...

随机推荐

  1. Spring Security + JJWT 实现 JWT 认证和授权

    关于 JJWT 的使用,可以参考之前的文章:JJWT 使用示例 一.鉴权过滤器 @Component public class JwtAuthenticationTokenFilter extends ...

  2. Spring Cloud Alibaba 初体验(六) Seata 及结合 MyBatis 与 MyBatis-Plus 的使用

    一.下载与运行 本文使用 Seata 1.1.0:https://github.com/seata/seata/releases Windows 环境下双击 bin/seata-server.bat ...

  3. 为什么 redo log 具有 crash-safe 的能力,是 binlog 无法替代的?

    昨天在复习 MySQL 日志相关的知识,学的东西过一段时间后就会遗忘,遗忘后再重新思考,往往会有新的收获.想到几个问题,把它记录下来. 为什么 redo log 具有 crash-safe 的能力,而 ...

  4. 利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

    利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果 前言 近日公司接到一个轨道系统的需求,需要将地铁线路及列车实时位置展示在大屏上.既然是大屏项目,那视觉效果当然是第一重点,咱们可以先来看看项 ...

  5. 安装rose遇到的问题

    将rose文件夹里面的rose.rar直接解压到rose安装目录里面的common文件夹中 安装教程与问题解决连接https://blog.csdn.net/Farewell_w/article/de ...

  6. 第15.46节、PyQt显示部件:OpenGL Widget部件功能简介及使用案例

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 老猿学5G博文目录 一.概述 OpenGL Widget部件是一个Op ...

  7. 第9.10节 Python中IO模块其他文件操作属性和方法简介

    本文中所有案例中的fp都是使用open函数打开文件返回的一个文件对象,为了节省篇幅,大部分没有提供文件打开的代码. 一. 文件是否关闭的属性 属性名:closed 功用:判断文件是否关闭 示例: &g ...

  8. 第10.2节 查看导入的Python模块

    在Python中,要查看导入模块,可以使用sys.modules来查看,不过sys包含了所有导入模块包括内建模块,如果需要过滤掉内建模块甚至扩展模块,则需要对sys.modules进行一下过滤. 一. ...

  9. Hbase API 多条件查询

    public static ResultScanner scan(String tableName, String family, List<String> columns, List&l ...

  10. 题解 CF1446D2 【Frequency Problem (Hard Version)】

    给出一个跑得快一点的做法,洛谷最优解 (时间是第二名的 \(\frac{1}{2}\)), CF 第一页 D1 首先找到整个序列的众数 \(G\), 很容易证明答案序列中的两个众数中其中一个是 \(G ...