java开发工作中对InheritableThreadLocal使用思考

作者:方圆想当图灵 时间:2023-11-24 21:46:44 

引言

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

1. 先说结论

InheritableThreadLocal 只有在父线程创建子线程时,在子线程中才能获取到父线程中的线程变量;

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

2. 实验例子

  • 创建一个线程数固定为1的线程池,先在main线程中存入变量1,并使用线程池开启新的线程打印输出线程变量,之后更改main线程的线程变量为变量2,再使用线程池中线程(发生线程复用)打印输出线程变量,对比两次输出的值是否不同

/**
* 测试线程池下InheritableThreadLocal线程变量失效的场景
*/
public class TestInheritableThreadLocal {
   private static final InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
   // 固定大小的线程池,保证线程复用
   private static final ExecutorService executorService = Executors.newFixedThreadPool(1);
   public static void main(String[] args) {
       threadLocal.set("main线程 变量1");
       // 正常取到 main线程 变量1
       executorService.execute(() -> System.out.println(threadLocal.get()));
       threadLocal.set("main线程 变量2");
       // 线程复用再取还是 main线程 变量1
       executorService.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<T> extends ThreadLocal<T> {
   /**
    * 当子线程被创建时,通过该方法来初始化子线程中线程变量的值,
    * 这个方法在父线程中被调用,并且在子线程开启之前。
    *
    * 通过重写这个方法可以改变从父线程中继承过来的值。
    *
    * @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类

java开发工作中对InheritableThreadLocal使用思考

3.2.1 childValue方法

  • 开启新线程时,会调用Thread的构造方法

public Thread(ThreadGroup group, String name) {
       init(group, null, name, 0);
   }
  • 沿着构造方法向下,找到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);
   ...
}

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<Object> key = (ThreadLocal<Object>) 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. 实验例子流程图

java开发工作中对InheritableThreadLocal使用思考

  • main线程set main线程 变量1时,会调用到InheritableThreadLocalcreateMap方法,创建 inheritableThreadLocals 并保存线程变量

  • 开启子线程1时,会深拷贝父线程中的线程变量到子线程中,如图示

  • main线程set main线程 变量2,会覆盖主线程中之前set的mian线程变量1

  • 最后发生线程复用,子线程1无法获取到main线程新set的值,仍然打印 main线程 变量1

来源:https://juejin.cn/post/7166046781216653326

标签:java,Inheritable,ThreadLocal
0
投稿

猜你喜欢

  • android底部弹出iOS7风格对话选项框(QQ对话框)--第三方开源之IOS_Dialog_Library

    2023-02-18 17:42:44
  • Java设计模式之代理模式_动力节点Java学院整理

    2021-08-24 05:55:18
  • C#环形缓冲区(队列)完全实现

    2022-06-26 08:05:48
  • 时间处理函数工具分享(时间戳计算)

    2021-07-24 05:06:18
  • JAVA实现SOCKET多客户端通信的案例

    2023-08-26 19:10:35
  • Java详细讲解堆排序与时间复杂度的概念

    2023-10-20 02:00:11
  • Android 重写ViewGroup 分析onMeasure()和onLayout()方法

    2023-10-27 23:58:06
  • C#语法糖(Csharp Syntactic sugar)大汇总

    2022-12-04 12:55:23
  • 生成8位随机不重复的数字编号的方法

    2023-11-26 07:00:29
  • 详解Java中的日期类

    2022-08-27 17:14:03
  • C#获取图片文件扩展名的方法

    2022-03-06 04:27:43
  • Flutter 图片开发核心技能快速掌握教程

    2023-08-16 22:01:51
  • spring多数据源配置实现方法实例分析

    2023-06-06 22:15:56
  • Android自定义简单的顶部标题栏

    2023-04-23 06:02:17
  • C#微信公众号开发 微信事件交互

    2023-04-22 21:18:31
  • 深入Android Browser配置管理的详解

    2021-09-11 20:48:54
  • Java实现超大Excel文件解析(XSSF,SXSSF,easyExcel)

    2023-11-27 11:02:17
  • android 网络请求库volley方法详解

    2022-01-01 01:34:30
  • Android音视频开发之MediaExtactor使用教程

    2023-03-20 19:25:11
  • Java如何处理延迟任务过程解析

    2022-04-29 15:11:37
  • asp之家 软件编程 m.aspxhome.com