前提
最近一两个月花了很大的功夫做UCloud
服务和中间件迁移到阿里云的工作,没什么空闲时间撸文。想起很早之前写过ThreadLocal
的源码分析相关文章,里面提到了ThreadLocal
存在一个不能向预先创建的线程中进行变量传递的局限性,刚好有一位HSBC
的技术大牛前同事提到了团队引入了transmittable-thread-local解决了此问题。借着这个契机,顺便clone
了transmittable-thread-local
源码进行分析,这篇文章会把ThreadLocal
和InheritableThreadLocal
的局限性分析完毕,并且从一些基本原理以及设计模式的运用分析transmittable-thread-local
(下文简称为TTL
)整套框架的实现。
这篇文章前后花了两周时间编写,行文比价干硬,文字比较多(接近5W
字),希望带着耐心阅读。
父子线程的变量传递
在Java
中没有明确给出一个API
可以基于子线程实例获取其父线程实例,有一个相对可行的方案就是在创建子线程Thread
实例的时候获取当前线程的实例,用到的API
是Thread#currentThread()
:
public class Thread implements Runnable { // 省略其他代码 @HotSpotIntrinsicCandidate public static native Thread currentThread(); // 省略其他代码 } 复制代码
Thread#currentThread()
方法是一个静态本地方法,它是由JVM
实现,这是在JDK
中唯一可以获取父线程实例的API
。一般而言,如果想在子线程实例中得到它的父线程实例,那么需要像如下这样操作:
public class InheritableThread { public static void main(String[] args) throws Exception{ // 父线程就是main线程 Thread parentThread = Thread.currentThread(); Thread childThread = new Thread(()-> { System.out.println("Parent thread is:" + parentThread.getName()); },"childThread"); childThread.start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } } // 输出结果: Parent thread is:main 复制代码
类似地,如果我们想把一个父子线程共享的变量实例传递,也可以这样做:
public class InheritableVars { public static void main(String[] args) throws Exception { // 父线程就是main线程 Thread parentThread = Thread.currentThread(); final Var var = new Var(); var.setValue1("var1"); var.setValue2("var2"); Thread childThread = new Thread(() -> { System.out.println("Parent thread is:" + parentThread.getName()); methodFrame1(var); }, "childThread"); childThread.start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } private static void methodFrame1(Var var) { methodFrame2(var); } private static void methodFrame2(Var var) { } @Data private static class Var { private Object value1; private Object value2; } } 复制代码
这种做法其实是可行的,子线程调用的方法栈中的所有方法都必须显示传入需要从父线程传递过来的参数引用Var
实例,这样就会产生硬编码问题,既不灵活也导致方法不能复用,所以才衍生出线程本地变量Thread Local
,具体的实现有ThreadLocal
和InheritableThreadLocal
。它们两者的基本原理是类似的,实际上所有的变量实例是缓存在线程实例的变量ThreadLocal.ThreadLocalMap
中,线程本地变量实例都只是线程实例获取ThreadLocal.ThreadLocalMap
的一道桥梁:
public class Thread implements Runnable { // 省略其他代码 // KEY为ThreadLocal实例,VALUE为具体的值 ThreadLocal.ThreadLocalMap threadLocals = null; // KEY为InheritableThreadLocal实例,VALUE为具体的值 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 省略其他代码 } 复制代码
ThreadLocal
和InheritableThreadLocal
之间的区别可以结合源码分析一下(见下一小节)。前面的分析听起来如果觉得抽象的话,可以自己写几个类推敲一下,假如线程其实叫ThrowableThread
,而线程本地变量叫ThrowableThreadLocal
,那么它们之间的关系如下:
public class Actor { static ThrowableThreadLocal THREAD_LOCAL = new ThrowableThreadLocal(); public static void main(String[] args) throws Exception { ThrowableThread throwableThread = new ThrowableThread() { @Override public void run() { methodFrame1(); } }; throwableThread.start(); } private static void methodFrame1() { THREAD_LOCAL.set("throwable"); methodFrame2(); } private static void methodFrame2() { System.out.println(THREAD_LOCAL.get()); } /** * 这个类暂且认为是java.lang.Thread */ private static class ThrowableThread implements Runnable { ThrowableThreadLocal.ThrowableThreadLocalMap threadLocalMap; @Override public void run() { } // 这里模拟VM的实现,返回ThrowableThread自身,大家先认为不是返回NULL public static ThrowableThread getCurrentThread() { // return new ThrowableThread(); return null; // <--- 假设这里在VM的实现里面返回的不是NULL而是当前的ThrowableThread } public void start() { run(); } } private static class ThrowableThreadLocal { public ThrowableThreadLocal() { } public void set(Object value) { ThrowableThread currentThread = ThrowableThread.getCurrentThread(); assert null != currentThread; ThrowableThreadLocalMap threadLocalMap = currentThread.threadLocalMap; if (null == threadLocalMap) { threadLocalMap = currentThread.threadLocalMap = new ThrowableThreadLocalMap(); } threadLocalMap.put(this, value); } public Object get() { ThrowableThread currentThread = ThrowableThread.getCurrentThread(); assert null != currentThread; ThrowableThreadLocalMap threadLocalMap = currentThread.threadLocalMap; if (null == threadLocalMap) { return null; } return threadLocalMap.get(this); } // 这里其实在ThreadLocal中用的是WeakHashMap public static class ThrowableThreadLocalMap extends HashMap<ThrowableThreadLocal, Object> { } } } 复制代码
上面的代码不能运行,只是通过一个自定义的实现说明一下其中的原理和关系。
ThreadLocal和InheritableThreadLocal的局限性
InheritableThreadLocal
是ThreadLocal
的子类,它们之间的联系是:两者都是线程Thread
实例获取ThreadLocal.ThreadLocalMap
的一个中间变量。区别是:两者控制ThreadLocal.ThreadLocalMap
创建的时机和通过Thread
实例获取ThreadLocal.ThreadLocalMap
在Thread
实例中对应的属性并不一样,导致两者的功能有一点差别。通俗来说两者的功能联系和区别是:
ThreadLocal
:单个线程生命周期强绑定,只能在某个线程的生命周期内对ThreadLocal
进行存取,不能跨线程存取。
public class ThreadLocalMain { private static ThreadLocal<String> TL = new ThreadLocal<>(); public static void main(String[] args) throws Exception { new Thread(() -> { methodFrame1(); }, "childThread").start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } private static void methodFrame1() { TL.set("throwable"); methodFrame2(); } private static void methodFrame2() { System.out.println(TL.get()); } } // 输出结果: throwable 复制代码
InheritableThreadLocal
:(1)可以无感知替代ThreadLocal
的功能,当成ThreadLocal
使用。(2)明确父-子线程关系的前提下,继承(拷贝)父线程的线程本地变量缓存过的变量,而这个拷贝的时机是子线程Thread
实例化时候进行的,也就是子线程实例化完毕后已经完成了InheritableThreadLocal
变量的拷贝,这是一个变量传递的过程。
public class InheritableThreadLocalMain { // 此处可以尝试替换为ThreadLocal,最后会输出null static InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>(); public static void main(String[] args) throws Exception { new Thread(() -> { // 在父线程中设置变量 ITL.set("throwable"); new Thread(() -> { methodFrame1(); }, "childThread").start(); }, "parentThread").start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } private static void methodFrame1() { methodFrame2(); } private static void methodFrame2() { System.out.println(ITL.get()); } } // 输出结果: throwable 复制代码
上面提到的两点可以具体参看ThreadLocal
、InheritableThreadLocal
和Thread
三个类的源码,这里笔者把一些必要的注释和源码段贴出:
// --> java.lang.Thread类的源码片段 public class Thread implements Runnable { // 省略其他代码 // 这是Thread最基本的构造函数 private Thread(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { // 省略其他代码 Thread parent = currentThread(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); // inheritThreadLocals一般情况下为true // 当前子线程实例拷贝父线程的inheritableThreadLocals属性,创建一个新的ThreadLocal.ThreadLocalMap实例赋值到自身的inheritableThreadLocals属性 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; this.tid = nextThreadID(); } // 省略其他代码 } // --> java.lang.ThreadLocal源码片段 public class ThreadLocal<T> { // 省略其他代码 public void set(T value) { Thread t = Thread.currentThread(); // 通过当前线程获取线程实例中的threadLocals ThreadLocalMap map = getMap(t); // 线程实例中的threadLocals为NULL,实例则创建一个ThreadLocal.ThreadLocalMap实例添加当前ThreadLocal->VALUE到ThreadLocalMap中,如果已经存在ThreadLocalMap则进行覆盖对应的Entry if (map != null) { map.set(this, value); } else { createMap(t, value); } } // 通过线程实例获取该线程的threadLocals实例,其实是ThreadLocal.ThreadLocalMap类型的属性 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } public T get() { Thread t = Thread.currentThread(); // 通过当前线程获取线程实例中的threadLocals,再获取ThreadLocal.ThreadLocalMap中匹配上KEY为当前ThreadLocal实例的Entry对应的VALUE ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 找不到则尝试初始化ThreadLocal.ThreadLocalMap return setInitialValue(); } // 如果不存在ThreadLocal.ThreadLocalMap,则通过初始化initialValue()方法的返回值,构造一个ThreadLocal.ThreadLocalMap private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } // 省略其他代码 } // --> java.lang.InheritableThreadLocal源码 - 太简单,全量贴出 public class InheritableThreadLocal<T> extends ThreadLocal<T> { // 这个方法使用在线程Thread的构造函数里面ThreadLocal.createInheritedMap(),基于父线程InheritableThreadLocal的属性创建子线程的InheritableThreadLocal属性,它的返回值决定了拷贝父线程的属性时候传入子线程的值 protected T childValue(T parentValue) { return parentValue; } // 覆盖获取线程实例中的绑定的ThreadLocalMap为Thread#inheritableThreadLocals,这个方法其实是覆盖了ThreadLocal中对应的方法,应该加@Override注解 ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } // 覆盖创建ThreadLocalMap的逻辑,赋值到线程实例中的inheritableThreadLocals,而不是threadLocals,这个方法其实是覆盖了ThreadLocal中对应的方法,应该加@Override注解 void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } } 复制代码
一定要注意,这里的setInitialValue()
方法很重要,一个新的线程Thread
实例在初始化(对于InheritableThreadLocal
而言继承父线程的线程本地变量)或者是首次调用ThreadLocal#set()
,会通过此setInitialValue()
方法去构造一个全新的ThreadLocal.ThreadLocalMap
,会直接使用createMap()
方法。
以前面提到的两个例子,贴一个图加深理解:
Example-1
:
Example-2
:
ThreadLocal
、InheritableThreadLocal
的最大局限性就是:无法为预先创建好(未投入使用)的线程实例传递变量(准确来说是首次传递某些场景是可行的,而后面由于线程池中的线程是复用的,无法进行更新或者修改变量的传递值),泛线程池Executor
体系、TimerTask
和ForkJoinPool
等一般会预先创建(核心)线程,也就它们都是无法在线程池中由预创建的子线程执行的Runnable
任务实例中使用。例如下面的方式会导致参数传递失败:
public class InheritableThreadForExecutor { static final InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>(); static final Executor EXECUTOR = Executors.newFixedThreadPool(1); public static void main(String[] args) throws Exception { ITL.set("throwable"); EXECUTOR.execute(() -> { System.out.println(ITL.get()); }); ITL.set("doge"); EXECUTOR.execute(() -> { System.out.println(ITL.get()); }); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } } // 输出结果: throwable throwable # <--- 可见此处参数传递出现异常 复制代码
首次变量传递成功是因为线程池中的所有子线程都是派生自main
线程。
TTL的简单使用
TTL
的使用方式在它的项目README.md
或者项目中的单元测试有十分详细的介绍,先引入依赖com.alibaba:transmittable-thread-local:2.11.4
,这里演示一个例子:
// 父-子线程 public class TtlSample1 { static TransmittableThreadLocal<String> TTL = new TransmittableThreadLocal<>(); public static void main(String[] args) throws Exception { new Thread(() -> { // 在父线程中设置变量 TTL.set("throwable"); new Thread(TtlRunnable.get(() -> { methodFrame1(); }), "childThread").start(); }, "parentThread").start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } private static void methodFrame1() { methodFrame2(); } private static void methodFrame2() { System.out.println(TTL.get()); } } // 输出: throwable // 线程池 public class TtlSample2 { static TransmittableThreadLocal<String> TTL = new TransmittableThreadLocal<>(); static final Executor EXECUTOR = Executors.newFixedThreadPool(1); public static void main(String[] args) throws Exception { TTL.set("throwable"); EXECUTOR.execute(TtlRunnable.get(() -> { System.out.println(TTL.get()); })); TTL.set("doge"); EXECUTOR.execute(TtlRunnable.get(() -> { System.out.println(TTL.get()); })); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } } // 输出: throwable doge 复制代码