一、问题的提出

在系统开发过程中常使用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框架使用c3po链接数据库

    编辑工具:idea 1.配置pom.xml文件(创建模板时软件自动创建) 导入spring的核心架包 全部架包官网:https://mvnrepository.com/ 1 <dependenc ...

  2. linux搭建harbor与使用

    条件:安装docker&docker-compose 如未安装,请看:linux离线安装docker + docker-compose harbor 1.下载 下载地址:https://git ...

  3. 使用PyQt开发图形界面Python应用专栏目录

    ☞ ░ 前往老猿Python博文目录 ░ 本专栏为收费专栏的文章目录,对应的免费专栏为<PyQt入门知识目录>,两个专栏都为基于PyQt的Python图形界面开发基础教程,只是收费专栏中的 ...

  4. 转:Python考核试题及答案

    Python测试(总分:120) 选择题(每题2分,共20分) 1.下列哪个语句在Python中是非法的? (B) A.x = y = z = 1 B.x = (y = z + 1) C.x, y = ...

  5. PyQt(Python+Qt)学习随笔:QTabWidget部件选项卡可用状态访问方法isTabEnabled、setTabEnabled

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTabWidget的每个选项卡及页面可设置是否可用,如果选项卡不可用时,则不能通过操作手工切换到该 ...

  6. 第12.5节 Python time模块导览

    一.时间相关的概念 time模块模块提供了各种时间相关的函数,在介绍时间相关功能前,先介绍一些术语和惯例: epoch 是时间开始的点,并且取决于平台.对于Unix, epoch 是1970年1月1日 ...

  7. CTF SHOW WEB_AK赛

    CTF SHOW平台的WEB AK赛: 签到_观己 <?php ​ if(isset($_GET['file'])){ $file = $_GET['file']; if(preg_match( ...

  8. 题解-CF348E Pilgrims

    题面 CF348E Pilgrims 有一棵 \(n\) 个点的 带权 树和 \(m\) 个关键点,要求杀了一个不关键的点,满足最多的关键点到离它最远的所有关键点的路径都被打断.求可以满足的最多关键点 ...

  9. 【Harbor学习笔记】-教你快速搭建Docker私有仓库

    目录 架构图 Harbor依赖的外部组件 Harbor自有组件 核心组件 安装 1. 下载离线安装包 2. 配置 harbor.cfg (harbor.yml) 3. 启动 Harbor 安装配置问题 ...

  10. 为什么类只能用public修饰?

    为什么类只能使用public修饰? 首先,类只能使用public修饰是一个伪命题,应该说我们只见到过使用public修饰的类,还有一些类没有访问修饰符,此时访问权限为default.其次,类实际上分为 ...