一、问题的提出

在系统开发过程中常使用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. 20200311_最新爬取mzitu

    废话不多, 直接上代码, python3.6: import requests from bs4 import BeautifulSoup import os import time; import ...

  2. android studio报butterknife错误

    Error:Execution failed for task ':shipper:javaPreCompileDebug'.> Annotation processors must be ex ...

  3. PyQt(Python+Qt)学习随笔:Designer中属性设置界面的属性字体使用粗黑体的含义

    老猿Python博文目录 老猿Python博客地址 使用了好几个月的Designer,今天才发现属性编辑界面的属性名有的为粗而黑,有的则不是,如图: 稍微测试了一下,发现是对属性值进行过调整,不再是缺 ...

  4. PostMan设置环境变量&全局变量

    一.设置环境变量 1.点击右上角Manage Environment,进入环境变量设置界面 2.定义环境名称,参数名及参数值 3.将接口地址中服务器地址进行参数化,并选择对应的环境执行 二.设置全局变 ...

  5. PHP代码审计分段讲解(4)

    08 SESSION验证绕过 源代码为: <?php ​ $flag = "flag"; ​ session_start(); if (isset ($_GET['passw ...

  6. es6 Object对象扩展新方法

    ES6给Object拓展了许多新的方法,如: keys(obj):获取对象的所有key形成的数组     var obj = { foo: 'bar', baz: 42 };  Object.keys ...

  7. 原生js获取页面所有的checkbox

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. Dwango Programming Contest 6th E 题解

    题目大意 你有一条区间\([0, X)\),并且有一个数组\(L_1, ..., L_n\).对于任意\(1 \leq i \leq n\),你可以指定一个非负整数\(0 \leq j_i \leq ...

  9. TortoiseGit和intellij idea配置秘钥

    第一:intellij idea 配置gitlab ssh key 1:打开git bash,不需要进入任何目录,直接输入 ssh-keygen -t rsa 一路回车 2:生成之后找到秘钥文件id_ ...

  10. EM 算法-对鸢尾花数据进行聚类

    公号:码农充电站pro 主页:https://codeshellme.github.io 之前介绍过K 均值算法,它是一种聚类算法.今天介绍EM 算法,它也是聚类算法,但比K 均值算法更加灵活强大. ...