🔍 一、两段代码本质区别
比较点 | 普通 ThreadLocal 示例 | TTL 示例(TransmittableThreadLocal) |
---|---|---|
上下文传递目标 | 主线程 → 子线程 / 线程池线程 | 主线程 → 子线程 / 线程池线程 |
是否能成功传递变量 | ❌ 无法传递,子线程中 userContext.get() 为 null | ✅ 成功传递,子线程中 ttl.get() == "trace-001" |
是否支持线程池复用 | ❌ ThreadLocal 无法感知线程池中的线程复用 | ✅ TTL 拦截线程任务提交,复制上下文给线程池中的工作线程 |
是否自动清理线程变量 | ❌ 需手动 remove() ,否则可能内存泄漏 | ⚠️ TTL 仍需配合 try-finally 手动清理(否则也可能残留) |
背后机制 | 每个线程自己维护变量副本,线程池不做变量清理或传递 | 使用装饰器 +任务包装,把上下文 "快照" 带入线程池 |
✅ 二、你贴的两个例子解析
🔸普通 ThreadLocal 示例(不可传递)
public void handleRequest(String token) {
userContext.set(token); // 设置线程上下文
try {
businessLogic(); // 在当前线程内可以读到 userContext.get() == token
} finally {
userContext.remove(); // 必须清理,避免线程池中污染下次请求
}
}
适用场景:
-
当前线程中使用是安全的
-
在主线程里执行、请求处理里传递变量没问题
-
❌ 不能用于线程池子线程任务中访问这个值
🔸TransmittableThreadLocal 示例(支持上下文传播)
TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();
ttl.set("trace-001");
ExecutorService executor = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
executor.submit(() -> {
// 能自动获取主线程中设置的 traceId
System.out.println(ttl.get()); // 输出 trace-001 ✅
});
核心点:
-
TTL 自动封装了上下文传递逻辑
-
即使线程池中线程被复用,也能让新任务正确拿到主线程的上下文副本
🎯 面试推荐话术
Q:ThreadLocal 和 TTL 的区别在哪里?为什么 TTL 能在线程池中传递上下文?
🧠 答题思路:
普通 ThreadLocal 在线程池中因为线程复用,不会传递主线程上下文;TransmittableThreadLocal 通过增强 Executor,在任务提交时将主线程上下文复制到子线程,解决上下文丢失问题。项目中用于 TraceId 传递、用户上下文等非常合适。
🧪 Bonus:图示记忆差异
普通 ThreadLocal:
主线程 ----[ThreadLocal]----> token
子线程 ----[ThreadLocal]----> null (不会自动传递)
TTL:
主线程 ----[TTL]----> trace-001
submit 时 → 拷贝 → 子线程 [TTL] = trace-001