1、思路
如何自定义线程池,实现父线程MDC的自动拷贝?创建线程池时,我们需要一个创建线程的工厂类,一般都是重写这个工厂类来实现的。
线程在执行前,先通过MDC.getCopyOfContextMap()获取父线程的MDC的拷贝,执行时,判断这个Map是否为空,不为空设置到自己的线程的MDC,重写工厂类就是在构造线程时,把这段关键代码“植入”到线程类里。
拷贝MDC还有一个时机,也就是在Runnable在提交到线程池的时候,这个方法更为简单,只需要创建几个Wapper,使用匿名类也可以不创建。以下为实现步骤
2. 创建MDC装饰器
实现Runnable和Callable的装饰器类:
import org.slf4j.MDC;
import java.util.Map;
import java.util.concurrent.Callable;
public class MDCRunnable implements Runnable {
private final Runnable runnable;
private final Map<String, String> contextMap;
public MDCRunnable(Runnable runnable) {
this.runnable = runnable;
this.contextMap = MDC.getCopyOfContextMap();
}
@Override
public void run() {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} finally {
MDC.clear();
}
}
}
public class MDCCallable<V> implements Callable<V> {
private final Callable<V> callable;
private final Map<String, String> contextMap;
public MDCCallable(Callable<V> callable) {
this.callable = callable;
this.contextMap = MDC.getCopyOfContextMap();
}
@Override
public V call() throws Exception {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
return callable.call();
} finally {
MDC.clear();
}
}
}
3. 配置线程池
创建自定义的线程池配置类:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean(name = "customThreadPool")
public Executor threadPoolTaskExecutor() {
// 这里按照常规方法正常创建你的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
// 这里返回一个匿名类,重写execute方法
return new Executor() {
@Override
public void execute(Runnable command) {
// 这里把线程包装一下
executor.execute(new MDCRunnable(command));
}
};
}
}
总结
以上两个Wapper,包括这个匿名类线程池,不难看出他们都是代理类,应用了代理模式进行增强。 这个匿名类线程池在execute前,把线程用MDCRunnable包装了一个,于是线程就有了自动拷贝MDC的能力,目前这是最简单的一种方式。