本文源码截取自:TransmittableThreadLocal官方开源仓库
使用版本:release - v2.11.5
最近在准备双十一压测相关的调整工作,需要将线程池改为都使用TTL包装过的,用于辅助pinpoint插件实现Trace信息传递。之前没怎么关注过TTL这个库,因此在改用TTL的同时,学习下其实现原理。
Jdk提供了InheritableThreadLocal
类,用于在父子线程间传递线程变量(ThreadLocal),实现原理就是在Thread类保存名为inheritableThreadLocals的成员属性(以InheritableThreadLocal
对象为Key的ThreadLocalMap),并在初始化创建子线程时,将父线程的inheritableThreadLocals赋给子线程,这部分逻辑在Thread.init()
方法内。
这种方式在线程只被创建和使用一次时是有效的,但对于使用线程池的场景下,由于线程被复用,初始化一次后,后续使用并不会走这个ThreadLocal传递的流程,导致后续提交的任务并不会继承到父线程的线程变量,同时,还会获取到当前任务线程被之前几次任务所修改变量值。
TTL官方github:alibaba/transmittable-thread-local
TransmittableThreadLocal(TTL)
是阿里开源的,用于解决异步执行时上下文传递的问题的组件,在InheritableThreadLocal
基础上,实现了线程复用场景下的线程变量传递功能。
同ThreadLocal:父线程使用TransmittableThreadLocal保存变量,子线程get取出。
增强Runnable或Callable
增强线程池
TtlExecutors.getTtlExecutor()
或getTtlExecutorService()、getTtlScheduledExecutorService()
获取装饰后的线程池run()
方法内取出变量(任务子线程)装饰线程池其实本质也是装饰Runnable,只是将这个逻辑移到了ExecutorServiceTtlWrapper.submit()
方法内,对所有提交的Runnable都进行包装:
在2.11.0版本后,还增加了对原生ThreadLocal的支持,主要是针对用户依赖的库中使用ThreadLocal,又无法修改其代码的情况。
相关说明:ThreadLocal integration #130
TransmittableThreadLocal.Transmitter.registerThreadLocal()
将ThreadLocal内的变量值缓存TtlRunnable
,提交到线程池run()
方法内取出变量(任务子线程)根据TransmittableThreadLocal的使用流程,其核心逻辑可以分成三个部分:设置线程变量 -> 构建TtlRunnable -> 提交线程池运行
当调用TransmittableThreadLocal.set()
设置变量值时,除了会通过调用super.set()
(ThreadLocal)设置当前线程变量外,还会执行addThisToHolder()
方法:
TransmittableThreadLocal内部维护了一个静态的线程变量holder,保存的是以TransmittableThreadLocal对象为Key的Map(这个map的值永远是null,也就是当做Set使用的)
设值时向获取holder传入this,保存发起set()操作的TransmittableThreadLocal对象
构建TtlRunnable对象时,会保存原Runnable对象引用,用于后续run()方法中业务代码的执行。另外还会调用TransmittableThreadLocal.Transmitter.capture()
方法,缓存当前主线程的线程变量:
当TtlRunnable对象被提交到线程池执行时,调用TtlRunnable.run()
:
注意此时已处于任务子线程环境中
这里会从Runnable对象取出缓存的线程变量captured,然后进行后续流程:
TransmittableThreadLocal.Transmitter.replay()
:
由于上一步已经将从父线程复制的线程变量都设置到当前子线程的ThreadLocal中,因此run()方法中直接通过ThreadLocal.get()即可读取继承自父线程的变量值。
TransmittableThreadLocal.Transmitter.restore()
:
首先,从使用上来看,不管是修饰Runnable还是修饰线程池,本质都是将Runnable增强为TtlRunnable。
而从实现线程变量传递的原理上来看,TTL做的实际上就是将原本与Thread绑定的线程变量,缓存一份到TtlRunnable对象中,在执行子线程任务前,将对象中缓存的变量值设置到子线程的ThreadLocal中以供run()方法的代码使用,然后执行完后,又恢复现场,保证不会对复用线程产生影响。