前言
博客《Java 并发编程之美(四):深入剖析 ThreadLocal》提到 ThreadLocal 变量的基本使用方式,ThreadLocal 是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。但是在实际的开发中,有这样的一种需求:父线程生成的变量需要传递到子线程中进行使用,那么在使用 ThreadLocal 似乎就解决不了这个问题。由于 ThreadLocal 设计之初就是为了绑定当前线程,如果希望当前线程的 ThreadLocal 能够被子线程使用,实现方式就会相当困难。在此背景下,InheritableThreadLocal 应运而生,使用 InheritableThreadLocal 这个变量就可以轻松的在子线程中依旧使用父线程中的本地变量。
ThreadLocal 与 InheritableThreadLocal 区别
ThreadLocal 声明的变量是线程私有的成员变量,每个线程都有该变量的副本,线程对变量的修改对其他线程不可见。
InheritableThreadLocal 声明的变量同样是线程私有的,但是子线程可以使用同样的 InheritableThreadLocal 类型变量从父线程继承 InheritableThreadLocal 声明的变量,父线程无法拿到其子线程的。即使可以继承,但是子线程对变量的修改对父线程也是不可见的。
对 InheritableThreadLocal 的理解
InheritableThreadLocal 类是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。
ThreadLocal 是不支持继承性的,所谓继承性也是针对父线程和子线程来说,代码示例:
1 | class Scratch {public static final ThreadLocal<String> localVariable = new ThreadLocal<>(); |
InheritableThreadLocal 用于子线程能够拿到父线程往 ThreadLocal 里设置的值,代码示例:
1 | class Scratch { |
深入解析 InheritableThreadLocal 类
InheritableThreadLocal 类重写了 ThreadLocal 的 3 个函数:
1 | public class InheritableThreadLocal<T> extends ThreadLocal<T> { |
InheritableThreadLocal 赋值
从源码上看,跟 ThreadLocal 不一样的无非是 ThreadLocalMap 的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那么秘密在那里呢?通过跟踪 Thread 的构造方法,你能够发现是在构造 Thread 对象的时候对父线程的 InheritableThreadLocal 进行了赋值。下面是 Thread 的部分源码:
1 | public class Thread implements Runnable { |
通过跟踪 Thread 的构造方法,我们发现只要父线程在构造子线程(调用 new Thread())的时候 inheritableThreadLocals 变量不为空。新生成的子线程会通过 ThreadLocal.createInheritedMap 方法将父线程 inheritableThreadLocals 变量有的对象复制到子线程的 inheritableThreadLocals 变量上。这样就完成了线程间变量的继承与传递。
ThreadLocal.createInheritedMap
1 | public class ThreadLocal<T> { |
InheritableThreadLocal 和线程池搭配使用存在的问题
问题展示
代码示例:
1 | class Scratch { |
前后两次调用获取的值是一开始赋值的值,因为线程池中是缓存使用过的线程,当线程被重复调用的时候并没有再重新初始化 init() 线程,而是直接使用已经创建过的线程,所以这里的值并不会被再次操作。因为实际的项目中线程池的使用频率非常高,每一次从线程池中取出线程不能够直接使用之前缓存的变量,所以要解决这一个问题,网上大部分是推荐使用 alibaba 的开源项目 transmittable-thread-local。
transmittable-thread-local
JDK 的 InheritableThreadLocal 类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的 ThreadLocal 值传递已经没有意义,应用需要的实际上是把任务提交给线程池时的 ThreadLocal 值传递到任务执行时。
在 ThreadLocal 的需求场景即是 TTL(装饰器模式)的潜在需求场景,如果你的业务需要『在使用线程池等会池化复用线程的组件情况下传递 ThreadLocal』则是 TTL 目标场景。下面是几个典型场景例子:1、分布式跟踪系统;2、日志收集记录系统上下文;3 应用容器或上层框架跨应用代码给下层 SDK 传递信息。
1 | <dependency> |
代码示例:
1 | class Scratch { |
整个过程的完整时序图:
总结
- ThreadLocal 和 InheritableThreadLocal 本质上只是为了方便编码给的工具类,具体存数据是 ThreadLocalMap 对象。
- ThreadLocalMap 存的 key 对象是 ThreadLocal,value 就是真正需要存的业务对象。
- Thread 里通过两个变量持用 ThreadLocalMap 对象,分别为:threadLocals 和 inheritableThreadLocals。
- InheritableThreadLocal 之所以能够完成线程间变量的传递,是在 newThread() 的时候对 inheritableThreadLocals 对象里的值进行了复制。
- 子线程通过继承得到的 InheritableThreadLocal 里的值与父线程里的 InheritableThreadLocal 的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写 InheritableThreadLocal 的 childValue 方法。
参考博文
[1]. ThreadLocal 和 InheritableThreadLocal 深入分析
[2]. transmittable-thread-local
[3]. InheritableThreadLocal 详解
Java 并发编程之美系列
- Java 并发编程之美(一):并发队列 Queue 原理剖析
- Java 并发编程之美(二):线程池 ThreadPoolExecutor 原理探究
- Java 并发编程之美(三):异步执行框架 Eexecutor
- Java 并发编程之美(四):深入剖析 ThreadLocal
- Java 并发编程之美(五):揭开 InheritableThreadLocal 的面纱
- Java 并发编程之美(六):J.U.C 之线程同步辅助工具类
- Java 并发编程之美(七):透彻理解 Java 并发编程
- Java 并发编程之美(八):循序渐进学习 Java 锁机制