工作中对InheritableThreadLocal使用的思考
创始人
2024-01-22 09:39:53
0

最近在工作中结合线程池使用 InheritableThreadLocal 出现了获取线程变量“错误”的问题,看了相关的文档和源码后在此记录。

1. 先说结论

InheritableThreadLocal 只有在父线程创建子线程时,在子线程中才能获取到父线程中的线程变量;当配合线程池使用时:“第一次在线程池中开启线程,能在子线程中获取到父线程的线程变量,而当该子线程开启之后,发生线程复用,该子线程仍然保留的是之前开启它的父线程的线程变量,而无法获取当前父线程中新的线程变量”,所以会发生获取线程变量错误的情况。

2. 实验例子

  • 创建一个线程数固定为1的线程池,先在main线程中存入变量1,并使用线程池开启新的线程打印输出线程变量,之后更改main线程的线程变量为变量2,再使用线程池中线程(发生线程复用)打印输出线程变量,对比两次输出的值是否不同
/*** 测试线程池下InheritableThreadLocal线程变量失效的场景*/
public class TestInheritableThreadLocal {private static final InheritableThreadLocal threadLocal = new InheritableThreadLocal<>();// 固定大小的线程池,保证线程复用private static final ExecutorService executorService = Executors.newFixedThreadPool(1);public static void main(String[] args) {threadLocal.set("main线程 变量1");// 正常取到 main线程 变量1executorService.execute(() -> System.out.println(threadLocal.get()));threadLocal.set("main线程 变量2");// 线程复用再取还是 main线程 变量1executorService.execute(() -> System.out.println(threadLocal.get()));}
}
复制代码

输出结果:

main线程 变量1

main线程 变量1

发现两次输出结果值相同,证明发生线程复用时,子线程获取父线程变量失效

3. 详解

3.1 JavaDoc

This class extends ThreadLocal to provide inheritance of values from parent thread to child thread: when a child thread is created, the child receives initial values for all inheritable thread-local variables for which the parent has values. Normally the child's values will be identical to the parent's; however, the child's value can be made an arbitrary function of the parent's by overriding the childValue method in this class. Inheritable thread-local variables are used in preference to ordinary thread-local variables when the per-thread-attribute being maintained in the variable (e.g., User ID, Transaction ID) must be automatically transmitted to any child threads that are created.

InheritableThreadLocal 继承了 ThreadLocal, 以能够让子线程能够从父线程中继承线程变量: 当一个子线程被创建时,它会接收到父线程中所有可继承的变量。通常情况下,子线程和父线程中的线程变量是完全相同的,但是可以通过重写 childValue 方法来使父子线程中的值不同。

当线程中维护的变量如UserId, TransactionId 等必须自动传递到新创建的任何子线程时,使用InheritableThreadLocal要优于ThreadLocal

3.2 源码

public class InheritableThreadLocal extends ThreadLocal {/*** 当子线程被创建时,通过该方法来初始化子线程中线程变量的值,* 这个方法在父线程中被调用,并且在子线程开启之前。* * 通过重写这个方法可以改变从父线程中继承过来的值。** @param parentValue the parent thread's value* @return the child thread's initial value*/protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}
复制代码

其中childValue方法来获取父线程中的线程变量的值,也可通过重写这个方法来将获取到的线程变量的值进行修改。

getMap方法和createMap方法中,可以发现inheritableThreadLocals变量,它是 ThreadLocalMap,在Thread类

3.2.1 childValue方法

  1. 开启新线程时,会调用Thread的构造方法
    public Thread(ThreadGroup group, String name) {init(group, null, name, 0);}
复制代码
  1. 沿着构造方法向下,找到init方法的最终实现,其中有如下逻辑:为当前线程创建线程变量以继承父线程中的线程变量
/*** @param inheritThreadLocals 为ture,代表是为 包含可继承的线程变量 的线程进行初始化*/
private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {...if (inheritThreadLocals && parent.inheritableThreadLocals != null)// 注意这里创建子线程的线程变量this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);...}
复制代码
  1. ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)创建子线程 InheritedMap 的具体实现

createInheritedMap 方法,最终会调用到 ThreadLocalMap私有构造方法,传入的参数parentMap即为父线程中保存的线程变量

    private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal key = (ThreadLocal) e.get();if (key != null) {// 注意!!! 这里调用了childValue方法Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}
复制代码 

这个方法会对父线程中的线程变量做深拷贝,其中调用了childValue方法来获取/初始化子线程中的值,并保存到子线程中

  • 由上可见,可继承的线程变量只是在线程被创建的时候进行了初始化工作,这也就能解释为什么在线程池中发生线程复用时不能获取到父线程线程变量的原因

4. 实验例子流程图

  1. main线程set main线程 变量1时,会调用到InheritableThreadLocalcreateMap方法,创建 inheritableThreadLocals 并保存线程变量
  2. 开启子线程1时,会深拷贝父线程中的线程变量到子线程中,如图示
  3. main线程set main线程 变量2,会覆盖主线程中之前set的mian线程变量1
  4. 最后发生线程复用,子线程1无法获取到main线程新set的值,仍然打印 main线程 变量1

相关内容

热门资讯

三警齐发!天津一区启动暴雨Ⅲ级... 刚刚,市规划资源局蓟州分局与蓟州区气象局联合发布地质灾害气象风险黄色预警:2025年7月27日17时...
少林寺通报:释永信涉嫌刑事犯罪... 情况通报 少林寺住持释永信涉嫌刑事犯罪,挪用侵占项目资金寺院资产;严重违反佛教戒律,长期与多名女性...
央广财评丨财政政策更加积极 有... 近日披露的上半年财政收支情况显示,全国一般公共预算支出达到14.13万亿元,同比增长3.4%。其中,...
今世缘“内涵”洋河营销翻车?董... 图源:图虫创意 业绩追赶、商标之战、营销争锋相对......行业深度调整期下,白酒江湖总有硝烟升起。...
牛市主升浪开启了?别急!珍惜布... 本周,A股市场上行,主要宽基指数都收获了或多或少的周涨幅,其中,科创50、微盘股涨幅居前。板块方面,...
阿森纳0-1落后!纽卡流畅反击... 直播吧07月27日讯 阿森纳vs纽卡第6分钟,纽卡流畅反击,托纳利助攻伊兰加破门!
原创 奥... 7月27日,奥地利外长贝娅特·迈因尔-赖辛格向《世界报》表示,维也纳已准备好讨论放弃中立并加入北约,...
帅!约克雷斯赛前亮相向球迷致意... 直播吧07月27日讯 帅!约克雷斯赛前亮相向球迷致意,身穿阿森纳14号球衣!
陕西一金店遭洪水20公斤黄金被... 7月25日,陕西吴起县受洪灾影响,县城北洛河边部分商铺进水。该县碧桂园翡翠公馆小区门口一家老凤祥门店...
张碧晨被“禁”唱《年轮》,对音... ▲7月25日,@汪苏泷工作室 发文引发关注。图/网络截图 近日,歌手张碧晨与歌曲《年轮》相关话题引发...