从源码分析,为什么要使用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 对象并返回。
相关推荐
寻月隐君1 小时前
Rust 实战:从零构建一个多线程 Web 服务器
后端·rust·github
Livingbody2 小时前
FastMCP In Action之 Server详解
后端
GetcharZp3 小时前
C++ Boost 从入门到精通:让你的代码飞起来
c++·后端
北'辰3 小时前
DeepSeek智能考试系统智能体
前端·后端·架构·开源·github·deepseek
hrrrrb3 小时前
【Spring Boot 快速入门】八、登录认证(二)统一拦截
hive·spring boot·后端
在未来等你4 小时前
RabbitMQ面试精讲 Day 16:生产者优化策略与实践
中间件·面试·消息队列·rabbitmq
_風箏5 小时前
OpenSSH【安装 03】远程代码执行漏洞CVE-2024-6387修复(cp: 无法创建普通文件“/usr/sbin/sshd“:文本文件忙问题处理)
后端
用户89535603282205 小时前
Gin 框架核心架构解析
后端·go
我是哪吒5 小时前
分布式微服务系统架构第164集:架构懂了就来了解数据库存储扩展千亿读写
后端·面试·github
UrbanJazzerati6 小时前
PowerShell 自动化实战:自动化为 Git Staged 内容添加 Issue 注释标记
后端·面试·shell