前提 最近一两个月花了很大的功夫做UCloud
服务和中间件迁移到阿里云的工作,没什么空闲时间撸文。想起很早之前写过ThreadLocal
的源码分析相关文章,里面提到了ThreadLocal
存在一个不能向预先创建的线程中进行变量传递的局限性,刚好有一位HSBC
的技术大牛前同事提到了团队引入了transmittable-thread-local 解决了此问题。借着这个契机,顺便clone
了transmittable-thread-local
源码进行分析,这篇文章会把ThreadLocal
和InheritableThreadLocal
的局限性分析完毕,并且从一些基本原理以及设计模式的运用分析transmittable-thread-local
(下文简称为TTL
)整套框架的实现。
如果对线程池和ThreadLocal
不熟悉的话,可以先参看一下前置文章:
这篇文章前后花了两周时间编写,行文比价干硬,文字比较多(接近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 { 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 { 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 { ThreadLocal.ThreadLocalMap threadLocals = null ; 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()); } private static class ThrowableThread implements Runnable { ThrowableThreadLocal.ThrowableThreadLocalMap threadLocalMap; @Override public void run () { } public static ThrowableThread getCurrentThread () { return null ; } 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 ); } 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 { 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
三个类的源码,这里笔者把一些必要的注释和源码段贴出:
public class Thread implements Runnable { 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); if (inheritThreadLocals && parent.inheritableThreadLocals != null ) this .inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this .stackSize = stackSize; this .tid = nextThreadID(); } } public class ThreadLocal <T > { public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { map.set(this , value); } else { createMap(t, value); } } ThreadLocalMap getMap (Thread t) { return t.threadLocals; } public T get () { Thread t = Thread.currentThread(); 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; } } return setInitialValue(); } 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; } } public class InheritableThreadLocal <T > extends ThreadLocal <T > { 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); } }
一定要注意,这里的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
TTL实现的基本原理 TTL
设计上使用了大量的委托(Delegate
),委托是C#
里面的说法,对标Java
的设计模式就是代理模式 。举个简单的例子:
@Slf4j public class StaticDelegate { public static void main (String[] args) throws Exception { new RunnableDelegate(() -> log.info("Hello World!" )).run(); } @Slf4j @RequiredArgsConstructor private static final class RunnableDelegate implements Runnable { private final Runnable runnable; @Override public void run () { try { log.info("Before run..." ); runnable.run(); log.info("After run..." ); } finally { log.info("Finally run..." ); } } } } 23 :45 :27.763 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - Before run...23 :45 :27.766 [main] INFO club.throwable.juc.StaticDelegate - Hello World!23 :45 :27.766 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - After run...23 :45 :27.766 [main] INFO club.throwable.juc.StaticDelegate$RunnableDelegate - Finally run...
委托如果使用纯熟的话,可以做出很多十分有用的功能,例如可以基于Micrometer
去统计任务的执行时间,上报到Prometheus
,然后用Grafana
做监控和展示:
@Slf4j public class MeterDelegate { public static void main (String[] args) throws Exception { Executor executor = Executors.newFixedThreadPool(1 ); Runnable task = () -> { try { Thread.sleep(1000 ); } catch (Exception ignore) { } }; Map<String, String> tags = new HashMap<>(8 ); tags.put("_class" , "MeterDelegate" ); executor.execute(new MicrometerDelegate(task, "test-task" , tags)); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } @Slf4j @RequiredArgsConstructor private static final class MicrometerDelegate implements Runnable { private final Runnable runnable; private final String taskType; private final Map<String, String> tags; @Override public void run () { long start = System.currentTimeMillis(); try { runnable.run(); } finally { long end = System.currentTimeMillis(); List<Tag> tagsList = Lists.newArrayList(); Optional.ofNullable(tags).ifPresent(x -> x.forEach((k, v) -> { tagsList.add(Tag.of(k, v)); })); Metrics.summary(taskType, tagsList).record(end - start); } } } }
委托理论上只要不线程栈溢出,可以无限层级地包装,有点像洋葱的结构,原始的目标方法会被包裹在最里面并且最后执行:
public static void main (String[] args) throws Exception { Runnable target = () -> log.info("target" ); Delegate level1 = new Delegate(target); Delegate level2 = new Delegate(level1); Delegate level3 = new Delegate(level2); } @RequiredArgsConstructor static class Delegate implements Runnable { private final Runnable runnable; @Override public void run () { runnable.run(); } }
当然,委托的层级越多,代码结构就会越复杂,不利于理解和维护。多层级委托这个洋葱结构,再配合Java
反射API
剥离对具体方法调用的依赖,就是Java
中切面编程的普遍原理,spring-aop
就是这样实现的。委托如果再结合Agent
和字节码增强(使用ASM
、Javassist
等),可以实现类加载时期替换对应的Runnable
、Callable
或者一般接口的实现,这样就能无感知完成了增强功能。此外,TTL
中还使用了模板方法模式 ,如:
@Slf4j public class TemplateMethod { public static void main (String[] args) throws Exception { Runnable runnable = () -> log.info("Hello World!" ); Template template = new Template(runnable) { @Override protected void beforeExecute () { log.info("BeforeExecute..." ); } @Override protected void afterExecute () { log.info("AfterExecute..." ); } }; template.run(); } @RequiredArgsConstructor static abstract class Template implements Runnable { private final Runnable runnable; protected void beforeExecute () { } @Override public void run () { beforeExecute(); runnable.run(); afterExecute(); } protected void afterExecute () { } } } 00 :25 :32.862 [main] INFO club.throwable.juc.TemplateMethod - BeforeExecute...00 :25 :32.865 [main] INFO club.throwable.juc.TemplateMethod - Hello World!00 :25 :32.865 [main] INFO club.throwable.juc.TemplateMethod - AfterExecute...
分析了两种设计模式,下面简单理解一下TTL
实现的伪代码:
# TTL extends InheritableThreadLocal # Holder of TTL -> InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> [? => NULL] (1 )创建一个全局的Holder,用于保存父线程(或者明确了父线程的子线程)的TTL对象,这里注意,是TTL对象,Holder是当作Set使用 (2 )(父)线程A中使用了TTL,则所有设置的变量会被TTL捕获 (3 )(子)线程B使用了TtlRunnable(Runnable的TTL实现,使用了前面提到的委托,像Callable的实现是TtlCallable),会重放所有存储在TTL中的,来自于线程A的存储变量 (4 )线程B重放完毕后,清理线程B独立产生的ThreadLocal变量,归还变TTL的变量
主要就是这几步,里面的话术有点抽象,后面一节分析源码的时候会详细讲解。
TTL的源码分析 主要分析:
框架的骨架。
核心类TransmittableThreadLocal
。
传递器Transmitter
。
捕获、重放和复原。
Agent
模块。
TTL框架骨架 TTL
是一个十分精悍的框架,它依赖少量的类实现了比较强大的功能,除了提供给用户使用的API
,还提供了基于Agent
和字节码增强实现了无感知增强泛线程池对应类的功能,这一点是比较惊艳的。这里先分析编程式的API
,再简单分析Agent
部分的实现。笔者阅读TTL
框架的时间是2020
年五一劳动节前后,当前的最新发行版本为2.11.4
。TTL
的项目结构很简单:
- transmittable-thread-local - com.alibaba.ttl - spi SPI接口和一些实现 - threadpool 线程池增强,包括ThreadFactory和线程池的Wrapper等 - agent 线程池的Agent实现相关 最外层的包有一些Wrapper的实现和TTL
先看spi
包:
- spi TtlAttachments TtlAttachmentsDelegate TtlEnhanced TtlWrapper
TtlEnhanced
是TTL
的标识接口(空接口),标识具体的组件被TTL
增强:
public interface TtlEnhanced {}
通过instanceof
关键字就可以判断具体的实现是否TTL
增强过的组件。TtlWrapper
接口继承自接口TtlEnhanced
,用于标记实现类可以解包装获得原始实例:
public interface TtlWrapper <T > extends TtlEnhanced { @NonNull T unwrap () ; }
TtlAttachments
接口也是继承自接口TtlEnhanced
,用于为TTL
添加K-V
结构的附件,TtlAttachmentsDelegate
是其实现类,K-V
的存储实际上是委托给ConcurrentHashMap
:
public interface TtlAttachments extends TtlEnhanced { void setTtlAttachment (@NonNull String key, Object value) ; <T> T getTtlAttachment (@NonNull String key) ; String KEY_IS_AUTO_WRAPPER = "ttl.is.auto.wrapper" ; } public class TtlAttachmentsDelegate implements TtlAttachments { private final ConcurrentMap<String, Object> attachments = new ConcurrentHashMap<String, Object>(); @Override public void setTtlAttachment (@NonNull String key, Object value) { attachments.put(key, value); } @Override @SuppressWarnings("unchecked") public <T> T getTtlAttachment (@NonNull String key) { return (T) attachments.get(key); } }
因为TTL
的实现覆盖了泛线程池Executor
、ExecutorService
、ScheduledExecutorService
、ForkJoinPool
和TimerTask
(在TTL
中组件已经标记为过期,推荐使用ScheduledExecutorService
),范围比较广,短篇幅无法分析所有的源码,而且它们的实现思路是基本一致的,笔者下文只会挑选Executor
的实现路线进行分析。
核心类TransmittableThreadLocal TransmittableThreadLocal
是TTL
的核心类,TTL
框架就是用这个类来命名的。先看它的构造函数和关键属性:
@FunctionalInterface public interface TtlCopier <T > { T copy (T parentValue) ; } public class TransmittableThreadLocal <T > extends InheritableThreadLocal <T > implements TtlCopier <T > { private static final Logger logger = Logger.getLogger(TransmittableThreadLocal.class.getName()); private final boolean disableIgnoreNullValueSemantics; public TransmittableThreadLocal () { this (false ); } public TransmittableThreadLocal (boolean disableIgnoreNullValueSemantics) { this .disableIgnoreNullValueSemantics = disableIgnoreNullValueSemantics; } }
disableIgnoreNullValueSemantics
属性相关可以查看Issue157 ,下文分析方法的时候也会说明具体的场景。简单来说就是:
disableIgnoreNullValueSemantics
为false
(默认值):启用set null to remove value
语义,也就是设置NULL
值相当于移除当前已经设置完的值,返回值会从initialValue()
或者childValue()
中获取
disableIgnoreNullValueSemantics
为true
(默认值):禁用set null to remove value
语义,该语义和ThreadLocal
语义一致,可以传递NULL
值和获取NULL
值
TransmittableThreadLocal
继承自InheritableThreadLocal
,本质就是ThreadLocal
,那它到底怎么样保证变量可以在线程池中的线程传递?接着分析其他所有方法:
public class TransmittableThreadLocal <T > extends InheritableThreadLocal <T > implements TtlCopier <T > { public T copy (T parentValue) { return parentValue; } protected void beforeExecute () { } protected void afterExecute () { } @Override public final T get () { T value = super .get(); if (disableIgnoreNullValueSemantics || null != value) addThisToHolder(); return value; } @Override public final void set (T value) { if (!disableIgnoreNullValueSemantics && null == value) { remove(); } else { super .set(value); addThisToHolder(); } } @Override public final void remove () { removeThisFromHolder(); super .remove(); } private void superRemove () { super .remove(); } private T copyValue () { return copy(get()); } private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>, ?>>() { @Override protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() { return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(); } @Override protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?> parentValue) { return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue); } }; @SuppressWarnings("unchecked") private void addThisToHolder () { if (!holder.get().containsKey(this )) { holder.get().put((TransmittableThreadLocal<Object>) this , null ); } } private void removeThisFromHolder () { holder.get().remove(this ); } private static void doExecuteCallback (boolean isBefore) { for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { try { if (isBefore) threadLocal.beforeExecute(); else threadLocal.afterExecute(); } catch (Throwable t) { if (logger.isLoggable(Level.WARNING)) { logger.log(Level.WARNING, "TTL exception when " + (isBefore ? "beforeExecute" : "afterExecute" ) + ", cause: " + t.toString(), t); } } } } static void dump (@Nullable String title) { if (title != null && title.length() > 0 ) { System.out.printf("Start TransmittableThreadLocal[%s] Dump...%n" , title); } else { System.out.println("Start TransmittableThreadLocal Dump..." ); } for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { System.out.println(threadLocal.get()); } System.out.println("TransmittableThreadLocal Dump end!" ); } static void dump () { dump(null ); } }
这里一定要记住holder
是全局静态的,并且它自身也是一个InheritableThreadLocal
(get()
方法也是线程隔离的),它实际上就是线程管理所有TransmittableThreadLocal
的桥梁。这里可以考虑一个单线程的例子来说明TransmittableThreadLocal
的存储架构:
public class TtlSample3 { static TransmittableThreadLocal<String> TTL1 = new TransmittableThreadLocal<>(); static TransmittableThreadLocal<String> TTL2 = new TransmittableThreadLocal<>(); static TransmittableThreadLocal<String> TTL3 = new TransmittableThreadLocal<>(); public static void main (String[] args) throws Exception { TTL1.set("VALUE-1" ); TTL2.set("VALUE-2" ); TTL3.set("VALUE-3" ); } }
这里简化了例子,只演示了单线程的场景,图中的一些对象的哈希码有可能每次启动JVM
实例都不一样,这里只是做示例:
注释里面也提到,holder
里面的WeakHashMap
是当成Set
容器使用,映射的值都是NULL
,每次遍历它的所有KEY
就能获取holder
里面的所有的TransmittableThreadLocal
实例,它是一个全局的存储器,但是本身是一个InheritableThreadLocal
,多线程共享后的映射关系会相对复杂:
再聊一下disableIgnoreNullValueSemantics
的作用,默认情况下disableIgnoreNullValueSemantics=false
,TTL
如果设置NULL
值,会直接从holder
移除对应的TTL
实例,在TTL#get()
方法被调用的时候,如果原来持有的属性不为NULL
,该TTL
实例会重新加到holder
。如果设置disableIgnoreNullValueSemantics=true
,则set(null)
的语义和ThreadLocal
一致。见下面的例子:
public class TtlSample4 { static TransmittableThreadLocal<Integer> TL1 = new TransmittableThreadLocal<Integer>(false ) { @Override protected Integer initialValue () { return 5 ; } @Override protected Integer childValue (Integer parentValue) { return 10 ; } }; static TransmittableThreadLocal<Integer> TL2 = new TransmittableThreadLocal<Integer>(true ) { @Override protected Integer initialValue () { return 5 ; } @Override protected Integer childValue (Integer parentValue) { return 10 ; } }; public static void main (String[] args) throws Exception { TL1.set(null ); TL2.set(null ); Thread t1 = new Thread(TtlRunnable.get(() -> { System.out.println(String.format("Thread:%s,value:%s" , Thread.currentThread().getName(), TL1.get())); }), "T1" ); Thread t2 = new Thread(TtlRunnable.get(() -> { System.out.println(String.format("Thread:%s,value:%s" , Thread.currentThread().getName(), TL2.get())); }), "T2" ); t1.start(); t2.start(); TimeUnit.SECONDS.sleep(Long.MAX_VALUE); } } Thread:T2,value:null Thread:T1,value:5
这是因为框架的设计者不想把NULL
作为有状态的值,如果真的有需要保持和ThreadLocal
一致的用法,可以在构造TransmittableThreadLocal
实例的时候传入true
。
传送器Transmitter
这里的Transmitter原来笔者理解为Reactor编程模型中的类似于(事件)发射器的命名,TTL作者哲良大哥指出更恰当的应该为传送器,这里于某次修订把Transmitter命名为传送器。
传送器Transmitter
是TransmittableThreadLocal
的一个公有静态类,它的核心功能是传输所有的TransmittableThreadLocal
实例和提供静态方法注册当前线程的变量到其他线程。按照笔者阅读源码的习惯,先看构造函数和关键属性:
public static class Transmitter { private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>(); private static final Object threadLocalHolderUpdateLock = new Object(); private static final Object threadLocalClearMark = new Object(); private static final TtlCopier<Object> shadowCopier = new TtlCopier<Object>() { @Override public Object copy (Object parentValue) { return parentValue; } }; private Transmitter () { throw new InstantiationError("Must not instantiate this class" ); } private static class Snapshot { final WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value; final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value; private Snapshot (WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value) { this .ttl2Value = ttl2Value; this .threadLocal2Value = threadLocal2Value; } } }
Transmitter
在设计上是一个典型的工具类,外部只能调用其公有静态方法。接着看其他静态方法:
public static class Transmitter { @NonNull public static Object capture () { return new Snapshot(captureTtlValues(), captureThreadLocalValues()); } private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() { WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new WeakHashMap<TransmittableThreadLocal<Object>, Object>(); for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) { ttl2Value.put(threadLocal, threadLocal.copyValue()); } return ttl2Value; } private static WeakHashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() { final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value = new WeakHashMap<ThreadLocal<Object>, Object>(); for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) { final ThreadLocal<Object> threadLocal = entry.getKey(); final TtlCopier<Object> copier = entry.getValue(); threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get())); } return threadLocal2Value; } @NonNull public static Object replay (@NonNull Object captured) { final Snapshot capturedSnapshot = (Snapshot) captured; return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value)); } @NonNull private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) { WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocal<Object>, Object>(); for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal<Object> threadLocal = iterator.next(); backup.put(threadLocal, threadLocal.get()); if (!captured.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } setTtlValuesTo(captured); doExecuteCallback(true ); return backup; } private static void setTtlValuesTo (@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> ttlValues) { for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry : ttlValues.entrySet()) { TransmittableThreadLocal<Object> threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> captured) { final WeakHashMap<ThreadLocal<Object>, Object> backup = new WeakHashMap<ThreadLocal<Object>, Object>(); for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) { final ThreadLocal<Object> threadLocal = entry.getKey(); backup.put(threadLocal, threadLocal.get()); final Object value = entry.getValue(); if (value == threadLocalClearMark) threadLocal.remove(); else threadLocal.set(value); } return backup; } public static void restore (@NonNull Object backup) { final Snapshot backupSnapshot = (Snapshot) backup; restoreTtlValues(backupSnapshot.ttl2Value); restoreThreadLocalValues(backupSnapshot.threadLocal2Value); } private static void restoreTtlValues (@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> backup) { doExecuteCallback(false ); for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) { TransmittableThreadLocal<Object> threadLocal = iterator.next(); if (!backup.containsKey(threadLocal)) { iterator.remove(); threadLocal.superRemove(); } } setTtlValuesTo(backup); } private static void restoreThreadLocalValues (@NonNull WeakHashMap<ThreadLocal<Object>, Object> backup) { for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) { final ThreadLocal<Object> threadLocal = entry.getKey(); threadLocal.set(entry.getValue()); } } }
这里三个核心方法,看起来比较抽象,要结合多线程的场景和一些空间想象进行推敲才能比较容易地理解:
capture()
:捕获操作,父线程原来就存在的线程本地变量映射和手动注册的线程本地变量映射捕获,得到捕获的快照值captured
。
reply()
:重放操作,子线程原来就存在的线程本地变量映射和手动注册的线程本地变量生成备份backup
,刷新captured
的所有值到子线程在全局存储器holder
中绑定的值。
restore()
:复原操作,子线程原来就存在的线程本地变量映射和手动注册的线程本地变量恢复成backup
。
setTtlValuesTo()
这个方法比较隐蔽,要特别要结合多线程和空间思维去思考,例如当入参是captured
,本质是从父线程捕获到的绑定在父线程的所有线程本地变量,调用的时机在reply()
和restore()
,这两个方法只会在子线程中调用,setTtlValuesTo()
里面拿到的TransmittableThreadLocal
实例调用set()
方法相当于把绑定在父线程的所有线程本地变量的值全部刷新到子线程当前绑定的TTL
中的线程本地变量的值,更深层次地想,是基于外部的传入值刷新了子线程绑定在全局存储器holder
里面绑定到该子线程的线程本地变量的值。
Transmitter
还有不少静态工具方法,这里不做展开,可以参考项目里面的测试demo
和README.md
进行调试。
捕获、重放和复原 其实上面一节已经介绍了Transmitter
提供的捕获、重放和复原的API
,这一节主要结合分析TtlRunnable
中的相关逻辑。TtlRunnable
的源码如下:
public final class TtlRunnable implements Runnable , TtlWrapper <Runnable >, TtlEnhanced , TtlAttachments { private final AtomicReference<Object> capturedRef; private final Runnable runnable; private final boolean releaseTtlValueReferenceAfterRun; private TtlRunnable (@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) { this .capturedRef = new AtomicReference<Object>(capture()); this .runnable = runnable; this .releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun; } @Override public void run () { Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null )) { throw new IllegalStateException("TTL value reference is released after run!" ); } Object backup = replay(captured); try { runnable.run(); } finally { restore(backup); } } @Nullable public static TtlRunnable get (@Nullable Runnable runnable) { return get(runnable, false , false ); } @Nullable public static TtlRunnable get (@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) { if (null == runnable) return null ; if (runnable instanceof TtlEnhanced) { if (idempotent) return (TtlRunnable) runnable; else throw new IllegalStateException("Already TtlRunnable!" ); } return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun); } }
其实关注点只需要放在构造函数、run()
方法,其他都是基于此做修饰或者扩展。构造函数的源码说明,capture()
在TtlRunnable
实例化的时候已经被调用,实例化它的一般就是父线程,所以整体的执行流程如下:
Agent模块 启用Agent
功能,需要在Java
的启动参数添加:-javaagent:path/to/transmittable-thread-local-x.yzx.jar
。原理是通过Instrumentation
回调激发ClassFileTransformer
实现目标类的字节码增强,使用到javassist
,被增强的类主要是泛线程池的类:
Executor
体系:主要包括ThreadPoolExecutor
和ScheduledThreadPoolExecutor
,对应的字节码增强类实现是TtlExecutorTransformlet
。
ForkJoinPool
:对应的字节码增强类实现是TtlForkJoinTransformlet
。
TimerTask
:对应的字节码增强类实现是TtlTimerTaskTransformlet
。
Agent
的入口类是TtlAgent
,这里查看对应的源码:
public final class TtlAgent { public static void premain (String agentArgs, @NonNull Instrumentation inst) { kvs = splitCommaColonStringToKV(agentArgs); Logger.setLoggerImplType(getLogImplTypeFromAgentArgs(kvs)); final Logger logger = Logger.getLogger(TtlAgent.class); try { logger.info("[TtlAgent.premain] begin, agentArgs: " + agentArgs + ", Instrumentation: " + inst); final boolean disableInheritableForThreadPool = isDisableInheritableForThreadPool(); final List<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>(); transformletList.add(new TtlExecutorTransformlet(disableInheritableForThreadPool)); transformletList.add(new TtlForkJoinTransformlet(disableInheritableForThreadPool)); if (isEnableTimerTask()) transformletList.add(new TtlTimerTaskTransformlet()); final ClassFileTransformer transformer = new TtlTransformer(transformletList); inst.addTransformer(transformer, true ); logger.info("[TtlAgent.premain] addTransformer " + transformer.getClass() + " success" ); logger.info("[TtlAgent.premain] end" ); ttlAgentLoaded = true ; } catch (Exception e) { String msg = "Fail to load TtlAgent , cause: " + e.toString(); logger.log(Level.SEVERE, msg, e); throw new IllegalStateException(msg, e); } } }
List<JavassistTransformlet>
作为参数传入ClassFileTransformer
的实现类TtlTransformer
中,其中的转换方法为:
public class TtlTransformer implements ClassFileTransformer { private final List<JavassistTransformlet> transformletList = new ArrayList<JavassistTransformlet>(); TtlTransformer(List<? extends JavassistTransformlet> transformletList) { for (JavassistTransformlet transformlet : transformletList) { this .transformletList.add(transformlet); logger.info("[TtlTransformer] add Transformlet " + transformlet.getClass() + " success" ); } } @Override public final byte [] transform(@Nullable final ClassLoader loader, @Nullable final String classFile, final Class<?> classBeingRedefined, final ProtectionDomain protectionDomain, @NonNull final byte [] classFileBuffer) { try { if (classFile == null ) return NO_TRANSFORM; final String className = toClassName(classFile); ClassInfo classInfo = new ClassInfo(className, classFileBuffer, loader); for (JavassistTransformlet transformlet : transformletList) { transformlet.doTransform(classInfo); if (classInfo.isModified()) return classInfo.getCtClass().toBytecode(); } } catch (Throwable t) { String msg = "Fail to transform class " + classFile + ", cause: " + t.toString(); logger.log(Level.SEVERE, msg, t); throw new IllegalStateException(msg, t); } return NO_TRANSFORM; } }
这里挑选TtlExecutorTransformlet
的部分方法来看:
@Override public void doTransform (@NonNull final ClassInfo classInfo) throws IOException, NotFoundException, CannotCompileException { if (EXECUTOR_CLASS_NAMES.contains(classInfo.getClassName())) { final CtClass clazz = classInfo.getCtClass(); for (CtMethod method : clazz.getDeclaredMethods()) { updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment(method); } } } private void updateSubmitMethodsOfExecutorClass_decorateToTtlWrapperAndSetAutoWrapperAttachment (@NonNull final CtMethod method) throws NotFoundException, CannotCompileException { final int modifiers = method.getModifiers(); if (!Modifier.isPublic(modifiers) || Modifier.isStatic(modifiers)) return ; CtClass[] parameterTypes = method.getParameterTypes(); StringBuilder insertCode = new StringBuilder(); for (int i = 0 ; i < parameterTypes.length; i++) { final String paramTypeName = parameterTypes[i].getName(); if (PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.containsKey(paramTypeName)) { String code = String.format( "$%d = %s.get($%d, false, true);" + "\ncom.alibaba.ttl.threadpool.agent.internal.transformlet.impl.Utils.setAutoWrapperAttachment($%<d);" , i + 1 , PARAM_TYPE_NAME_TO_DECORATE_METHOD_CLASS.get(paramTypeName), i + 1 ); logger.info("insert code before method " + signatureOfMethod(method) + " of class " + method.getDeclaringClass().getName() + ": " + code); insertCode.append(code); } } if (insertCode.length() > 0 ) method.insertBefore(insertCode.toString()); }
上面分析的方法的功能,就是让java.util.concurrent.ThreadPoolExecutor
和java.util.concurrent.ScheduledThreadPoolExecutor
的字节码被增强,提交的java.lang.Runnable
类型的任务会被包装为TtlRunnable
,提交的java.util.concurrent.Callable
类型的任务会被包装为TtlCallable
,实现了无入侵无感知地嵌入TTL
的功能。
小结 TTL
在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal
值的传递功能,解决异步执行时上下文传递的问题。 它是一个Java标准库,为框架/中间件设施开发提供的标配能力,项目代码精悍,只依赖了javassist
做字节码增强,实现Agent
模式下的近乎无入侵提供TTL
功能的特性。TTL
能在业务代码中实现透明/自动完成所有异步执行上下文的可定制、规范化的捕捉/传递,如果恰好碰到异步执行时上下文传递的问题,建议可以尝试此库。
参考资料:
(本文完 c-14-d e-a-20200502 r-a-20200116)