使用 TransmittableThreadLocal 的步骤及核心原理

zhidiantech · · 9 次点击 · · 开始浏览    
### 使用 `TransmittableThreadLocal` 的步骤及核心原理 #### **一、TransmittableThreadLocal 的作用** `TransmittableThreadLocal` 是阿里巴巴开源的工具类,用于在多线程环境(尤其是线程池)中**跨线程传递线程本地变量(ThreadLocal)**。它解决了传统 `ThreadLocal` 和 `InheritableThreadLocal` 在线程池中无法正确传递上下文的问题。 --- #### **二、核心使用场景** 1. **线程池任务提交**:确保线程池中的任务能访问提交线程的上下文(如 TraceID、用户信息)。 2. **异步编程**:在异步回调或 CompletableFuture 中传递上下文。 3. **分布式跟踪**:在微服务调用链中保持请求上下文的一致性。 --- #### **三、使用步骤** ##### 1. **添加依赖** 在项目中引入 `transmittable-thread-local` 依赖: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.14.2</version> </dependency> ``` ##### 2. **定义 TransmittableThreadLocal** 创建 `TransmittableThreadLocal` 实例并设置初始值: ```java private static final TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>(); ``` ##### 3. **设置和获取值** 在父线程中设置值: ```java context.set("request-id-123"); ``` 在子线程(如线程池任务)中获取值: ```java String value = context.get(); // 输出 "request-id-123" ``` ##### 4. **装饰线程池** 通过 `TtlExecutors` 包装线程池,使任务能自动传递上下文: ```java ExecutorService executor = Executors.newFixedThreadPool(4); ExecutorService ttlExecutor = TtlExecutors.getTtlExecutorService(executor); // 提交任务 ttlExecutor.submit(() -> { System.out.println("Context in task: " + context.get()); }); ``` ##### 5. **手动传递上下文(非线程池场景)** 使用 `TtlRunnable` 或 `TtlCallable` 包装任务: ```java Runnable task = () -> { System.out.println("Context: " + context.get()); }; Runnable ttlTask = TtlRunnable.get(task); new Thread(ttlTask).start(); ``` --- #### **四、核心原理** ##### 1. **上下文捕获与恢复** - **捕获阶段**:在任务提交时,通过 `TransmittableThreadLocal.Transmitter.capture()` 捕获当前线程的所有 `TransmittableThreadLocal` 变量值,生成一个快照(`Map<TransmittableThreadLocal<?>, Object>`)。 - **恢复阶段**:在任务执行前,通过 `TransmittableThreadLocal.Transmitter.replay()` 将快照中的值设置到执行线程的 `TransmittableThreadLocal` 中。 - **清理阶段**:任务执行完成后,调用 `TransmittableThreadLocal.Transmitter.restore()` 恢复执行线程原有的上下文,避免污染后续任务。 ##### 2. **任务包装机制** - 通过 `TtlRunnable` 或 `TtlExecutors` 装饰任务,重写 `run()` 方法: ```java public void run() { Map<TransmittableThreadLocal<?>, Object> captured = capture(); try { replay(captured); delegate.run(); // 执行原始任务 } finally { restore(); } } ``` ##### 3. **TransmittableThreadLocal 注册** - 所有 `TransmittableThreadLocal` 实例会通过 `WeakHashMap` 自动注册到全局管理器,确保垃圾回收后自动移除,避免内存泄漏。 ##### 4. **线程池兼容性** - 通过装饰线程池(`TtlExecutors`),确保所有提交的任务都会被包装,从而自动处理上下文传递。 --- #### **五、关键代码实现** ##### 1. **TransmittableThreadLocal 类** ```java public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> { // 全局注册表(WeakHashMap 防止内存泄漏) private static final Set<TransmittableThreadLocal<?>> holder = Collections.newSetFromMap(new WeakHashMap<>()); public TransmittableThreadLocal() { holder.add(this); } @Override public final T get() { return super.get(); } @Override public final void set(T value) { super.set(value); } } ``` ##### 2. **Transmitter 类(上下文快照)** ```java public static class Transmitter { // 捕获当前线程的上下文 public static Map<TransmittableThreadLocal<?>, Object> capture() { Map<TransmittableThreadLocal<?>, Object> captured = new HashMap<>(); for (TransmittableThreadLocal<?> ttl : holder) { captured.put(ttl, ttl.copyValue()); } return captured; } // 恢复上下文到执行线程 public static Map<TransmittableThreadLocal<?>, Object> replay(Map<TransmittableThreadLocal<?>, Object> captured) { Map<TransmittableThreadLocal<?>, Object> backup = new HashMap<>(); for (TransmittableThreadLocal<?> ttl : holder) { backup.put(ttl, ttl.get()); ttl.set(captured.get(ttl)); } return backup; } // 恢复执行线程的原始上下文 public static void restore(Map<TransmittableThreadLocal<?>, Object> backup) { for (TransmittableThreadLocal<?> ttl : holder) { ttl.set(backup.get(ttl)); } } } ``` --- #### **六、注意事项** 1. **避免内存泄漏**:及时调用 `remove()` 清理不再需要的上下文。 2. **线程池装饰**:确保所有任务通过 `TtlExecutors` 提交。 3. **兼容性**:不支持 `ForkJoinPool`,需使用 `TtlForkJoinPoolHelper` 处理。 --- #### **七、总结** `TransmittableThreadLocal` 通过 **任务装饰** 和 **上下文快照** 机制,在任务提交时捕获当前线程的上下文,并在执行线程中恢复该上下文,最终清理恢复原状态。其核心在于透明地传递线程本地变量,适用于线程池、异步编程等复杂多线程场景。
9 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传