TransmittableThreadLocal(TTL)实现线程变量传递的原理分析

本文源码截取自:TransmittableThreadLocal官方开源仓库

使用版本:release - v2.11.5

最近在准备双十一压测相关的调整工作,需要将线程池改为都使用TTL包装过的,用于辅助pinpoint插件实现Trace信息传递。之前没怎么关注过TTL这个库,因此在改用TTL的同时,学习下其实现原理。

一、背景

InheritableThreadLocal及其局限性

Jdk提供了InheritableThreadLocal类,用于在父子线程间传递线程变量(ThreadLocal),实现原理就是在Thread类保存名为inheritableThreadLocals的成员属性(以InheritableThreadLocal对象为Key的ThreadLocalMap),并在初始化创建子线程时,将父线程的inheritableThreadLocals赋给子线程,这部分逻辑在Thread.init()方法内。

这种方式在线程只被创建和使用一次时是有效的,但对于使用线程池的场景下,由于线程被复用,初始化一次后,后续使用并不会走这个ThreadLocal传递的流程,导致后续提交的任务并不会继承到父线程的线程变量,同时,还会获取到当前任务线程被之前几次任务所修改变量值。

二、TTL介绍

TTL官方github:alibaba/transmittable-thread-local

TransmittableThreadLocal(TTL)是阿里开源的,用于解决异步执行时上下文传递的问题的组件,在InheritableThreadLocal基础上,实现了线程复用场景下的线程变量传递功能。

主要使用方式:

1.直接使用

同ThreadLocal:父线程使用TransmittableThreadLocal保存变量,子线程get取出。

2.提交线程池使用

  1. 增强Runnable或Callable

    1. 使用TtlRunnable.get()或TtlCallable.get()
    2. 提交线程池之后,在run()内取出变量
  2. 增强线程池

    1. 使用TtlExecutors.getTtlExecutor()getTtlExecutorService()、getTtlScheduledExecutorService()获取装饰后的线程池
    2. 使用线程池提交普通任务
    3. run()方法内取出变量(任务子线程)

装饰线程池其实本质也是装饰Runnable,只是将这个逻辑移到了ExecutorServiceTtlWrapper.submit()方法内,对所有提交的Runnable都进行包装:

image-20200807095719400

3.对Jdk自带ThreadLocal支持

在2.11.0版本后,还增加了对原生ThreadLocal的支持,主要是针对用户依赖的库中使用ThreadLocal,又无法修改其代码的情况。

相关说明:ThreadLocal integration #130

  1. 使用Jdk自带ThreadLocal
    1. 调用TransmittableThreadLocal.Transmitter.registerThreadLocal()将ThreadLocal内的变量值缓存
    2. 构建TtlRunnable,提交到线程池
    3. run()方法内取出变量(任务子线程)

三、核心原理分析

根据TransmittableThreadLocal的使用流程,其核心逻辑可以分成三个部分:设置线程变量 -> 构建TtlRunnable -> 提交线程池运行

1.设置线程变量

当调用TransmittableThreadLocal.set()设置变量值时,除了会通过调用super.set()(ThreadLocal)设置当前线程变量外,还会执行addThisToHolder()方法:

image-20200807103557739

  • TransmittableThreadLocal内部维护了一个静态的线程变量holder,保存的是以TransmittableThreadLocal对象为Key的Map(这个map的值永远是null,也就是当做Set使用的)

    • holder保存了当前线程下的所有TTL线程变量
  • 设值时向获取holder传入this,保存发起set()操作的TransmittableThreadLocal对象

2.构建TtlRunnable对象

构建TtlRunnable对象时,会保存原Runnable对象引用,用于后续run()方法中业务代码的执行。另外还会调用TransmittableThreadLocal.Transmitter.capture()方法,缓存当前主线程的线程变量:
image-20200807111215487

  • 这里实际上就是对第一步在holder中保存的ThreadLocal对象进行遍历,保存其变量值
  • 此时原本通过ThreadLocal保存的和Thread绑定的线程变量,就复制了一份到TtlRunnable对象中了

3.在子线程中读取变量

当TtlRunnable对象被提交到线程池执行时,调用TtlRunnable.run()

注意此时已处于任务子线程环境中

BC10853C-DF6B-45B5-9AEB-B90081D1EF6D

这里会从Runnable对象取出缓存的线程变量captured,然后进行后续流程:

(1)前序处理

TransmittableThreadLocal.Transmitter.replay()
在这里插入图片描述

  • 将缓存的父线程变量值设置到当前任务线程(子线程)的ThreadLocal内,并将父线程的线程变量备份

(2)执行run()方法,读取变量值

由于上一步已经将从父线程复制的线程变量都设置到当前子线程的ThreadLocal中,因此run()方法中直接通过ThreadLocal.get()即可读取继承自父线程的变量值。

(3)后续处理

TransmittableThreadLocal.Transmitter.restore()
348D0F21-632E-48A2-B9F2-DC9AE10F2EF3

  • 将run()执行前获取的备份,设置到当前线程中去,恢复run()执行过程中可能导致的变化,避免对后续复用此线程的任务产生影响

整个流程可参考官方给出的时序图帮助理解:
9A0033F8-DCD4-4054-8E83-55511D5E52CC

四、总结

首先,从使用上来看,不管是修饰Runnable还是修饰线程池,本质都是将Runnable增强为TtlRunnable。

而从实现线程变量传递的原理上来看,TTL做的实际上就是将原本与Thread绑定的线程变量,缓存一份到TtlRunnable对象中,在执行子线程任务前,将对象中缓存的变量值设置到子线程的ThreadLocal中以供run()方法的代码使用,然后执行完后,又恢复现场,保证不会对复用线程产生影响。

  • 18
    点赞
  • 49
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在 Java 中,线程之间的变量传递是一件比较复杂的事情,因为线程之间是相互独立的,每个线程都有自己的栈空间和变量存储空间。在多线程编程中,我们经常需要在线程之间传递某些变量或者数据,这就需要使用一些特殊的技术来实现。 其中一种技术就是使用 TransmittableThreadLocalTTL)类,它可以让我们在多个线程之间传递变量,而且还可以保证变量的值在每个线程中都是唯一的。 TTL 是一个线程局部变量的扩展,它可以让我们在一个线程中创建一个变量,并且在任何一个子线程中都能够访问到这个变量的值。而且,当子线程结束时,TTL 会自动回收这个变量。 下面是一个使用 TTL 的示例代码: ``` import com.alibaba.ttl.TransmittableThreadLocal; public class Demo { private static TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>(); public static void main(String[] args) { // 在主线程中设置值 threadLocal.set("value set in main thread"); // 创建子线程并启动 Thread thread = new Thread(() -> { // 在子线程中获取值 String value = threadLocal.get(); System.out.println("value in sub thread: " + value); }); thread.start(); } } ``` 在这个示例代码中,我们首先创建了一个 TTL 对象 threadLocal,然后在主线程中设置了一个值,接着创建了一个子线程,并在子线程中获取了这个值。当子线程结束时,TTL 会自动回收这个变量TTL原理比较简单,它使用了 Java 中的 InheritableThreadLocal 类来实现。当我们在主线程中创建了一个 TTL 变量时,它会将这个变量存储到 InheritableThreadLocal 中。当我们创建子线程时,子线程会继承主线程的 InheritableThreadLocal 对象,并且会在自己的线程空间中创建一个新的 TTL 变量,这个变量的值会从主线程中的变量中拷贝过来。当子线程结束时,TTL 会自动回收这个变量。 总的来说,TTL 是一个非常方便的工具,它可以让我们在多个线程之间传递变量,而且还能够保证变量的值在每个线程中都是唯一的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值