前言
ThreadLocal
ThreadLocal 关键 API
ThreadLocal 存储结构
ThreadLocal 局限性
前言
ThreadLocal
ThreadLocal 关键 API
ThreadLocal 存储结构
ThreadLocal 局限性
InheritableThreadLocal
InheritableThreadLocal 的特性
InheritableThreadLocal 局限性
TransmittableThreadLocal
TransmittableThreadLocal 实现原理
整个过程的完整时序图
小结
前言
说起本地线程专属变量,大家首先会想到的是 JDK 默认提供的 ThreadLocal,用来存储在整个链路中都需要访问的数据,并且是线程安全的。由于在落地全链路压测的过程中,一个基本并核心的功能需求是流量标记需要在整个链路中进行传递,那么线程上下文环境成为解决这个问题最合适的技术。
ThreadLocal
ThreadLocal 关键 API
ThreadLocal 对外提供的关键 API 如下:
//从线程上下文中获取值 public T get() ; //将值设入线程上下文中,供同一线程后续使用 public void set(T value) ; //清除线程上下文 public void remove() ;
ThreadLocal 存储结构
上述 API 使用简单,关键是要理解 ThreadLocal 的内部存储结构:
ThreadLocal 的存储结构是这样的:
每个 Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
ThreadLocal 能在每个线程间进行隔离,其主要是靠在每个 Thread 对象中维护一个 ThreadLocalMap 来实现的。
ThreadLocal 局限性
ThreadLocal 无法在父子线程之间传递,示例代码如下:
public class ThreadLocalDemo { private static final ThreadLocal requestIdThreadLocal = new ThreadLocal<>(); public static void main(String[] args) { Integer reqId = new Integer(5); ThreadLocalDemo threadLocalExample = new ThreadLocalDemo(); threadLocalExample.setRequestId(reqId); } public void setRequestId(Integer requestId) { requestIdThreadLocal.set(requestId); doBussiness(); } public void doBussiness() { System.out.println("首先打印requestId:" + requestIdThreadLocal.get()); (new Thread(new Runnable() { @Override public void run() { System.out.println("子线程启动"); System.out.println("在子线程中访问requestId:" + requestIdThreadLocal.get()); } })).start(); } }
在 doBusiness 方法又启动了一个子线程来执行业务(模拟异步处理)
运行结果如下:
首先打印requestId:5 子线程启动 在子线程中访问requestId:null
从结果上来看,在子线程中无法访问在父线程中设置的本地线程变量,即子线程中无法获取到 ThreadLocal 中的 value,从上面的存储原理分析中,已经很明白了,子线程拥有自己的 ThreadLocalMap,自然无法获取父线程ThreadLocalMap 中的值。
但往往很多操作是需要异步操作的,因此父子线程直接共享 ThreadLocal 中的值是有必要的,那我们该如何来解决该问题呢?
为了解决该问题,JDK 引入了另外一个线程本地变量实现类 InheritableThreadLocal,下面介绍以下InheritableThreadLocal,看下它是如何实现父子线程之间共享线程上下文的?
InheritableThreadLocal
由于 ThreadLocal 在父子线程交互中子线程无法访问到存储在父线程中的值,无法满足某些场景的需求,例如链路跟踪,例如如下场景:
为了解决上述问题,JDK 引入了 InheritableThreadLocal,即子线程可以访问父线程中的线程本地变量,更严谨的说法是子线程可以访问在创建子线程时父线程当时的本地线程变量,因为其实现原理就是在创建子线程将父线程当前存在的本地线程变量拷贝到子线程的本地线程变量中。
ThreadLocal 的拷贝发生在:当前线程生成子线程实例的时候。如果当前线程的 inheritableThreadLocals 属性不为空,就会把该属性拷贝到子线程的 inheritableThreadLocals 属性中。
Thread 的 init 相关逻辑如下:
if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
赋值拷贝代码如下:
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