1. 背景与问题
在多线程编程中,ThreadLocal
常用于保存线程本地的变量(如用户会话、日志跟踪ID等),确保每个线程独立访问自己的数据。但传统 ThreadLocal
存在一个致命缺陷 :
当任务提交到线程池时,ThreadLocal
的值无法自动传递到线程池的工作线程中 ,导致上下文丢失。
例如:
java
ThreadLocal<String> context = new ThreadLocal<>();
ExecutorService executor = Executors.newFixedThreadPool(2);
context.set("user123"); // 主线程设置值
executor.submit(() -> {
System.out.println(context.get()); // 输出 null(值丢失)
});
2. TransmittableThreadLocal 的解决方案
阿里巴巴开源的 TransmittableThreadLocal(TTL) 解决了这一问题。它通过以下机制实现线程池中的上下文传递:
- 任务提交时 :捕获当前线程的
TTL
值(生成快照)。 - 任务执行时:将快照中的值恢复到线程池的工作线程中。
- 任务完成后:清理工作线程中的上下文,避免污染后续任务。
3. 工作原理
-
提交任务
- 调用
executor.submit(task)
时,TTL 自动调用Transmitter.capture()
捕获当前线程的所有TTL
值。 - 将捕获的快照附加到任务中。
- 调用
-
执行任务
- 工作线程执行任务前,TTL 通过
Transmitter.replay(snapshot)
将快照值注入工作线程的TTL
。 - 任务内通过
get()
获取到的是提交时的上下文值。
- 工作线程执行任务前,TTL 通过
-
清理阶段
- 任务执行完成后,调用
Transmitter.restore()
恢复工作线程的原始上下文。
- 任务执行完成后,调用
4. 典型场景:跨线程记录统一的日志标签
4.1、自定义 MDCTransmittableThreadLocal 值
java
public class MDCTransmittableThreadLocal extends TransmittableThreadLocal<Map<String, String>> {
/**
* 在线程池中执行任务前调用,MDCTransmittableThreadLocal中的值赋值到本线程的MDC中
*/
@Override
protected void beforeExecute() {
Map<String, String> mdc = get();
mdc.forEach(MDC::put);
}
/**
* 在线程池中执行任务后调用,清楚本线程MDC值 .MDCTransmittableThreadLocal值由线程池清理
*/
@Override
protected void afterExecute() {
MDC.clear();
}
/**
* 当线程第一次访问 TransmittableThreadLocal 时调用。
* @return
*/
@Override
protected Map<String, String> initialValue() {
return Maps.newHashMap();
}
}
4.2、定义filter拦截请求
java
public class RequestStreamFilter implements Filter {
private static MDCTransmittableThreadLocal ttlMDC = new MDCTransmittableThreadLocal();
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
//为每一个请求创建一个ID,方便查找日志时可以根据ID查找出一个http请求所有相关日志
String uuid=UUIDUtil.getUUID();
MDC.put(SystemConstants.TRACE_ID_KEY,uuid );
ttlMDC.get().put(SystemConstants.TRACE_ID_KEY, uuid);//跨线程池传递使用
chain.doFilter(request, response);
} finally {
MDC.clear();
ttlMDC.get().clear();
ttlMDC.remove();
}
}
@Override
public void destroy() {
}
}
4.3、测试代码
java
public static void test() throws Exception {
//单一线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//需要使用TtlExecutors对线程池包装一下
executorService= TtlExecutors.getTtlExecutorService(executorService);
//TransmittableThreadLocal创建
TransmittableThreadLocal<String> name = new TransmittableThreadLocal<>();
String s1 = UUID.randomUUID().toString();
String s2 = UUID.randomUUID().toString();
System.out.println("MDC1:"+s1);
System.out.println("MDC2:"+s2);
MDC.put("traceId", s1);
for (int i = 0; i < 5; i++) {
if(i%2 == 0){
ttlMDC.get().put("traceId", s2);
}else{
ttlMDC.get().put("traceId", s1);
}
name.set("🗡光如影"+i);
Thread.sleep(3000);
CompletableFuture.runAsync(()-> {
System.out.println(name.get());
System.out.println("MDC:"+MDC.get("traceId"));
System.out.println(Thread.currentThread().getName());
},executorService);
}
name.remove();
CompletableFuture.runAsync(()-> System.out.println(name.get()),executorService);
executorService.shutdown();
}