Java 线程池中如何正确使用 MDC

Java 线程池中的 MDC 不会可靠地自动传播。提交前捕获快照,执行前设置或清空,finally 恢复原上下文,避免复用线程及 CallerRunsPolicy 下的上下文污染。

最佳实践
应用程序模块协作插画

一句话回答:以 SLF4J + Logback 为例,MDC 按线程保存,线程池不会可靠地传播调用方上下文。提交任务时捕获 MDC 快照,执行前保存原上下文并设置快照(快照为空时也要清空),在 finally 中恢复原上下文。所有提交入口都使用包装器才会生效。

问题本质

// ❌ 错误示范:在提交线程设置 MDC
MDC.put("requestId", "req-123");
executor.submit(() -> {
    log.info("处理任务");   // 日志里没有 requestId——MDC 没跟过来
});

MDC 存在 ThreadLocal 里,submit 时设置的是主线程的 ThreadLocal,工作线程的 MDC 是空的。反过来,工作线程设了 MDC 没清,下一个复用该线程的任务会读到上一个任务的 requestId——日志张冠李戴。

正确姿势:装饰器包装

import java.util.Map;
import org.slf4j.MDC;

public class MdcTaskWrapper {
    public static Runnable wrap(Runnable task) {
        Map<String, String> context = MDC.getCopyOfContextMap();  // 提交时快照
        return () -> {
            Map<String, String> previous = MDC.getCopyOfContextMap();
            try {
                if (context == null) MDC.clear();
                else MDC.setContextMap(context);
                task.run();
            } finally {
                if (previous == null) MDC.clear();
                else MDC.setContextMap(previous);
            }
        };
    }
}

// 使用
executor.submit(MdcTaskWrapper.wrap(() -> {
    log.info("这条日志带上了 requestId");
}));

三个要素缺一不可:捕获提交上下文、覆盖执行上下文、finally 恢复原上下文。示例中的 executor 与 log 由应用提供;Callable 同样在 finally 恢复。提交方请求结束时也要清理自己写入的字段。用两个不同 requestId 连续复用同一工作线程,检查任务日志没有串号;复制 MDC 只传递日志字段,不会自行创建或传播父子 Span。

更省心的现成方案

  • TransmittableThreadLocal:传播 TTL 管理的变量;普通日志框架 MDC 不会仅因包装线程池或安装 agent 就自动变成 TTL,需要针对日志后端做桥接并验证。
  • Spring 环境:用 TaskDecorator 注册到 ThreadPoolTaskExecutor,官方支持的扩展点:
executor.setTaskDecorator(MdcTaskWrapper::wrap);

常见问题(FAQ)

Q:MDC.put 之后同一线程后面的日志都有了?

A:正是 ThreadLocal 的"粘性"——忘记 MDC.clear() 或 MDC.remove("key"),线程池场景必然串扰。团队规范:put 与 clear 成对出现在 try/finally 里。

Q:CompletableFuture 链式调用 MDC 也丢?

A:丢。链上的每个 thenApplyAsync 都可能换线程。解法相同:包装执行器传入(supplyAsync(task, ttlExecutor)),并验证每个异步边界;不要为传播日志字段盲目改成同步执行。

Q:MDC 存什么最有价值?

A:requestId/traceId(排障主线)、userId/tenantId(业务维度)、接口路径。别放大对象,字段数量按排障与隐私需要控制,pattern 里 %X{requestId} 输出。


参考资料

本文依据官方资料核对,未进行现场运行测试;代码与配置示例需结合实际版本、权限和环境验证。

延伸阅读

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台