https://github.com/alibaba/transmittable-thread-local 解决线程Local值,在线程池里没有传递的问题。
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.12.1</version> </dependency>
使用类TransmittableThreadLocal来保存值,并跨线程池传递。 TransmittableThreadLocal继承InheritableThreadLocal,使用方式也类似
简单使用: TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// ===================================================== // 在父线程中设置 context.set("value-set-in-parent"); // =====================================================
// 在子线程中可以读取,值是"value-set-in-parent" String value = context.get(); // =====================================================
线程池需要装饰一下,两种办法,二选一即可。 (1)使用TtlRunnable和TtlCallable来修饰传入线程池的Runnable和Callable。 Runnable task = new RunnableTask(); // 额外的处理,生成修饰了的对象ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable);
(2)修饰线程池,省去每次Runnable和Callable传入线程池时的修饰,这个逻辑可以在线程池中完成。
通过工具类TtlExecutors完成,有下面的方法: getTtlExecutor:修饰接口Executor getTtlExecutorService:修饰接口ExecutorService getTtlScheduledExecutorService:修饰接口ScheduledExecutorService
ExecutorService executorService = ... // 额外的处理,生成修饰了的对象executorService executorService = TtlExecutors.getTtlExecutorService(executorService);
(3)核心就是,装饰之后,在执行子线程之前,会保存当前的线程Local的map 在子线程里面,会先恢复线程Local的Map,然后调用实际执行的run方法或call方法。
(4)注意,代码必须修改,使用TransmittableThreadLocal来保存值。 如果不能修改代码,比如第三方jar,人家还是使用原始的线程Local,则还是不能传递。 需要手动包装一下。
package xxx.yyy;
import org.slf4j.MDC;
import java.util.Map; import java.util.concurrent.Callable;
public class MdcUtils { /** * 包装Runnable以传递MDC上下文 */ public static Runnable wrap(final Runnable runnable) { // 捕获当前MDC上下文 final Map<String, String> context = MDC.getCopyOfContextMap(); return new Runnable() { @Override public void run() { try { if (context != null) { MDC.setContextMap(context); } runnable.run(); } finally { MDC.clear(); } } }; }
/** * 包装Callable以传递MDC上下文 */ public static <T> Callable<T> wrap(final Callable<T> callable) { // 捕获当前MDC上下文 final Map<String, String> context = MDC.getCopyOfContextMap(); return new Callable<T>() { @Override public T call() throws Exception { try { if (context != null) { MDC.setContextMap(context); } return callable.call(); } finally { MDC.clear(); } } }; } }
executor.submit(MdcUtils.wrap(new Runnable() { @Override public void run() {
System.out.println(Thread.currentThread().getName() + " - "+MDC.get("userId")); } }));
|