从源码分析,为什么要使用TTL(TransmittableThreadLocal)而不是inheritableThreadLocals

一、inheritableThreadLocals的局限性

有时候比如记录日志,会有跨多线程的情况,并且很多情况下会使用到线程池,涉及到不同线程之间的传值,一般是父子线程,那就是使用 inheritableThreadLocals,但是它会有一个问题,即只会在线程第一次创建的时候进行拷贝。

ini 复制代码
   if (!attached) {
            if ((characteristics & NO_INHERIT_THREAD_LOCALS) == 0) {
                ThreadLocal.ThreadLocalMap parentMap = parent.inheritableThreadLocals;
                if (parentMap != null && parentMap.size() > 0) {
                    this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parentMap);
                }
                if (VM.isBooted()) {
                    this.contextClassLoader = contextClassLoader(parent);
                }
            } else if (VM.isBooted()) {
                // default CCL to the system class loader when not inheriting
                this.contextClassLoader = ClassLoader.getSystemClassLoader();
            }
        }

如果是线程池,如果改变了值,则子线程拿不到最新值了

csharp 复制代码
public class ITLDemo {
    private static final InheritableThreadLocal<String> ITL = new InheritableThreadLocal<>();

    public static void main(String[] args) throws Exception {

        System.out.println("===== 演示 ITL 局限 =====");
        limitation();
    }

  

    private static void limitation() throws Exception {
        ITL.set("v1");
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool.submit(() -> System.out.println("limit-1: " + ITL.get()));
        ITL.set("v2");
        pool.submit(() -> System.out.println("limit-2: " + ITL.get()));
        pool.shutdown();
    }
}

===== 演示 ITL 局限 =====
v1
v1
可以看到,并没有及时进行更新。
就是因为在 Thread.java中,只有在创建的时候,才会拷贝一次。

二、使用TTL进行改造

csharp 复制代码
  private static final TransmittableThreadLocal<String> TTL= new TransmittableThreadLocal<String>();

private static void TTL() throws Exception {
        TTL.set("v1");
        ExecutorService pool = Executors.newFixedThreadPool(1);
        pool = TtlExecutors.getTtlExecutorService(pool);
        pool.submit(() -> System.out.println("TTL-1: " + TTL.get()));
        Thread.sleep(1000);
//        这里变化了,但是 只会在第一次创建线程的时候进行拷贝
        TTL.set("v2");
        pool.submit(() -> System.out.println("TTL-2: " + TTL.get()));
        pool.shutdown();
    }

=======演示TTL=========
TTL-1: v1
TTL-2: v2

为什么TTL能实现?

1. 包装 ExecutorService

ini 复制代码
这里是关键代码
  ExecutorService pool = Executors.newFixedThreadPool(1);
        pool = TtlExecutors.getTtlExecutorService(pool);	

包装如下

less 复制代码
    @Nullable
    @Contract(
        value = "null -> null; !null -> !null",
        pure = true
    )
    public static ExecutorService getTtlExecutorService(@Nullable ExecutorService executorService) {
        return (ExecutorService)(!TtlAgent.isTtlAgentLoaded() && executorService != null && !(executorService instanceof TtlEnhanced) ? new ExecutorServiceTtlWrapper(executorService, true) : executorService);
    }

本质上,是将当前的 ExecutorService 包装成了ExecutorServiceTtlWrapper

scala 复制代码
@SuppressFBWarnings({"EQ_DOESNT_OVERRIDE_EQUALS"})
class ExecutorServiceTtlWrapper extends ExecutorTtlWrapper implements ExecutorService, TtlEnhanced {
    private final ExecutorService executorService;

    ExecutorServiceTtlWrapper(@NonNull ExecutorService executorService, boolean idempotent) {
        super(executorService, idempotent);
        this.executorService = executorService;
    }

2. 重写的 submit

将 task 包装成一个 TtlRunnable

less 复制代码
  @NonNull
    public Future<?> submit(@NonNull Runnable task) {
        return this.executorService.submit(TtlRunnable.get(task, false, this.idempotent));
    }

3. 核心逻辑

typescript 复制代码
 public void run() {
        Object captured = this.capturedRef.get();
        if (captured != null && (!this.releaseTtlValueReferenceAfterRun || this.capturedRef.compareAndSet(captured, (Object)null))) {
            Object backup = Transmitter.replay(captured);

            try {
                this.runnable.run();
            } finally {
                Transmitter.restore(backup);
            }

        } else {
            throw new IllegalStateException("TTL value reference is released after run!");
        }
    }

4. 捕获 ThreadLocal 的值

ini 复制代码
Object captured = this.capturedRef.get();
  • capturedRef 是一个 AtomicReference,用于存储捕获的 ThreadLocal 值。
  • captured 是当前线程捕获的 ThreadLocal 值。

5. 检查捕获的值是否有效

kotlin 复制代码
if (captured != null && (!this.releaseTtlValueReferenceAfterRun || this.capturedRef.compareAndSet(captured, (Object)null))) {
  • 检查 captured 是否为 null
  • 如果 releaseTtlValueReferenceAfterRunfalse,或者通过 compareAndSet 成功将 capturedRef 设置为 null,则继续执行。
    • releaseTtlValueReferenceAfterRun 是一个布尔标志,表示是否在任务执行后释放捕获的值。
    • compareAndSet 用于原子性地将 capturedRef 设置为 null,确保值只被释放一次。

6. 恢复 ThreadLocal 的值

ini 复制代码
Object backup = Transmitter.replay(captured);
  • Transmitter.replay(captured) 方法将捕获的 ThreadLocal 值恢复到当前线程。
  • backup 是当前线程的原始 ThreadLocal 值,用于在任务执行后恢复。

重点:threadLocal是怎么拷贝的

在TtlRunnable中Transmitter.capture()

kotlin 复制代码
public final class TtlRunnable implements Runnable, TtlWrapper<Runnable>, TtlEnhanced, TtlAttachments {
    private final AtomicReference<Object> capturedRef = new AtomicReference(Transmitter.capture());
    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
        this.runnable = runnable;
        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
    }
typescript 复制代码
 @NonNull
        public static Object capture() {
            HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap(transmitteeSet.size());

            for(Transmittee<Object, Object> transmittee : transmitteeSet) {
                try {
                    // 获取当前线程的 TransmittableThreadLocal 值
                    transmittee2Value.put(transmittee, transmittee.capture());
                } catch (Throwable t) {
                    if (TransmittableThreadLocal.logger.isLoggable(Level.WARNING)) {
                        TransmittableThreadLocal.logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
                    }
                }
            }

            return new Snapshot(transmittee2Value);
        }

capture() 方法的逻辑

typescript 复制代码
@NonNull
public static Object capture() {
    HashMap<Transmittee<Object, Object>, Object> transmittee2Value = new HashMap(transmitteeSet.size());

    for (Transmittee<Object, Object> transmittee : transmitteeSet) {
        try {
            transmittee2Value.put(transmittee, transmittee.capture());
        } catch (Throwable t) {
            if (TransmittableThreadLocal.logger.isLoggable(Level.WARNING)) {
                TransmittableThreadLocal.logger.log(Level.WARNING, "exception when Transmitter.capture for transmittee " + transmittee + "(class " + transmittee.getClass().getName() + "), just ignored; cause: " + t, t);
            }
        }
    }

    return new Snapshot(transmittee2Value);
}
  • 创建 transmittee2Value
    创建一个 HashMap,用于存储每个 Transmittee 对象及其对应的值。
  • 遍历 transmitteeSet
    遍历 transmitteeSet 中的每个 Transmittee 对象,调用其 capture() 方法捕获当前线程的值,并将结果存储到 transmittee2Value 中。
  • 捕获值
    调用 transmittee.capture() 方法捕获当前线程的值。如果捕获过程中发生异常,会记录警告日志并忽略该 Transmittee
  • 返回 Snapshot
    将捕获的值封装成一个 Snapshot 对象并返回。
相关推荐
回家路上绕了弯11 分钟前
为什么 AI 写代码时,总喜欢“防御性编程”?
后端
灯澜忆梦14 分钟前
【Redis中间件】#4 | 进阶数据类型语法
redis·后端
广州山泉婚姻17 分钟前
前后端分离的核心优势
前端·后端
IT_陈寒36 分钟前
Vue的响应式让我加班到凌晨,问题竟出在这个不起眼的地方
前端·人工智能·后端
jianqiang.xue42 分钟前
如何做好IT类的技术面试?
面试·职场和发展
乱码三千1 小时前
使用 Docker-compose 搭建SRS直播中转服务端
后端·架构·github
y1su1 小时前
【Leetcode】1477. 找两个和为目标值且不重叠的子数组
数据结构·后端·算法·leetcode·职场和发展
愛芳芳1 小时前
Swagger2 多环境动态配置实战指南
java·后端·maven·springboot·swagger
JaguarJack2 小时前
PHP 8.6 只是个小版本,三十项弃用却已在为 PHP 9.0 铺路
后端·php·服务端
BingoGo2 小时前
PHP 8.6 只是个小版本,三十项弃用却已在为 PHP 9.0 铺路
后端·php