java线程池不自动继承主线程mdc,需手动传递traceid:http入口用filter生成并绑定,线程池任务需封装traceablerunnable/callable或使用taskdecorator,completablefuture和定时任务须特别处理。

Java线程池本身不会自动继承主线程的MDC上下文,因为MDC底层依赖ThreadLocal,而线程池复用线程后,新任务执行时ThreadLocal内容仍是上一次任务残留或为空。所以必须在任务提交前显式捕获当前MDC,并在子线程执行时还原,否则日志里traceId会丢失或错乱。
HTTP入口处生成并绑定TraceId
使用Filter统一拦截请求,在进入业务逻辑前生成traceId并写入MDC:
- 若前端已带X-Trace-Id请求头,直接取值;否则用UUID生成唯一ID
- 调用MDC.put("traceId", traceId)绑定到当前线程
- 务必在finally块中MDC.remove("traceId"),防止线程复用导致脏数据
线程池任务提交前手动封装MDC
不能直接submit原始Runnable/Callable,需包装成能携带上下文的代理任务:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 定义TraceableRunnable:构造时快照当前MDC(MDC.getCopyOfContextMap()),run时先MDC.setContextMap(...)再执行原逻辑
- 同理实现TraceableCallable,适用于有返回值的异步场景
- 提交任务时统一走封装类:executor.submit(new TraceableRunnable(runnable))
使用ThreadPoolTaskExecutor + TaskDecorator
Spring推荐方式,避免重复造轮子,且支持全局配置:
- 声明@Bean类型为ThreadPoolTaskExecutor,而非Executors工具类创建的裸池
- 设置setTaskDecorator,例如注入一个自定义MdcTaskDecorator,内部完成MDC拷贝与还原
- 该装饰器会在每次execute/submit前自动触发,覆盖所有任务入口
CompletableFuture与定时任务特别处理
这两类场景容易被忽略,但恰恰是链路断裂高发区:
- supplyAsync或runAsync必须传入已装饰的线程池,禁用默认ForkJoinPool
- xxl-job、@Scheduled等定时任务,每次执行都是新线程,需在方法开头重新生成或从上下文提取traceId并put进MDC
- 若用Quartz,可通过JobDataMap透传,或在JobListener中注入traceId
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










