阿里TTL(Transmittable Thread Local)实现原理
TTL简介
TTL全称Transmittable Thread Local,是阿里开源的一个用于解决线程池场景下,ThreadLocal变量无法在子线程中继承的问题。
TTL核心原理简介
首先我们知道InheritableThreadLocal允许在创建子线程时,子线程继承(复制)父线程的值,但这个继承操作是在构造方法中实现的,也就是线程第一次创建的时候实现的。因为这个特点,在线程池场景下,InheritableThreadLocal子线程就没有办法继承父线程的值了。
那么TTL是怎么做到的?在介绍原理之前,我们先来简单的使用一下InheritableThreadLocal。
TTL使用案例
在下面的代码中
1、首先我们定义了一个TransmittableThreadLocal的context变量
2、我们创建了只有一个线程的线程池,并提交了一个任务,模拟线程池中的线程已经被创建
3、后面我们在主线程中,往context中设置一个值
4、后面提交了俩次任务。第一次提交,使用未包装的任务,子线程无法获取到主线程设置的值。第二次提交使用TtlRunnable对任务进行了包装,子线程成功获取到了值
5、在第二次任务中,子线程修改了context中的值,但最终主线程的值并不会受到影响
public class TTLDemo {// 1. 定义TTL变量private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();public static void main(String[] args) throws InterruptedException, ExecutionException {ExecutorService executor = Executors.newFixedThreadPool(1);executor.submit(() -> System.out.println("初始化线程" + Thread.currentThread())).get();// 2. 主线程设置值context.set("Global-Context-Value");System.out.println("[Main] 设置值: " + context.get());// 3. 提交任务(未包装)- 无法传递值!executor.submit(() -> System.out.println("[Task-RAW] 读取值: " + context.get()) // 输出 null);// 4. 创建可传递任务Runnable task = () -> {System.out.println("[Task-TTL] 读取值: " + context.get());// 子线程修改值(不会影响父线程)context.set("Sub-Thread-Modified");System.out.println("[Task-TTL] 修改后: " + context.get());};// 5. ⭐ 关键:包装任务Runnable ttlTask = TtlRunnable.get(task);// 6. 提交包装后的任务executor.submit(ttlTask); // 7. 主线程值保持不变Thread.sleep(500);System.out.println("[Main] 最终值: " + context.get()); // 仍为 Global-Context-Valueexecutor.shutdown();}
}
在上面的案例中,我们提交任务前,需要使用TtlRunnable.get()对要提交的任务包装一层,这样ttl才能生效。但如果每次都需要我们自己包装,就有点麻烦。这时候我们可以使用自动包装线程池,由线程池自动来帮助我们实现任务的包装。修改后的代码如下,
使用TtlExecutors.getTtlExecutorService()对我们的线程池进行包装。
// 1. 定义TTL变量private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();public static void main(String[] args) throws InterruptedException, ExecutionException {ExecutorService executor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));executor.submit(() -> System.out.println("初始化线程" + Thread.currentThread())).get();// 2. 主线程设置值context.set("Global-Context-Value");System.out.println("[Main] 设置值: " 