1、背景
近期需求开发过程中,有些查询希望进行异步整合,以及一些操作也希望异步化,所以使用了多线程进行优化,翻看代码发现大部分需要多线程操作的地方都是由开发自己维护了一个线程池,东一榔头西一棒槌的,不统一也不便于维护,于是想维护一些线程池,既达到目的,也方便进行维护,同时进行线程池资源隔离,防止线程耗尽阻塞整个服务。
2、异步线程池配置
Java
@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig {
private static final String TRACE_ID = "traceId";
private static final String DEFAULT_THREAD_NAME_PREFIX = "DEFAULT_THREAD_POOL";
@Bean("defaultExecutor")
public Executor defaultExecutor() {
ThreadPoolTaskExecutor executor = new TracingThreadPoolTaskExecutor();
int availableProcessors = Runtime.getRuntime().availableProcessors();
// 核心线程数
executor.setCorePoolSize(availableProcessors);
// 最大线程数
executor.setMaxPoolSize(availableProcessors * 2);
// 等待队列大小
executor.setQueueCapacity(availableProcessors * 20);
// 线程存活时间(秒)
executor.setKeepAliveSeconds(60);
// 线程名前缀
executor.setThreadNamePrefix(DEFAULT_THREAD_NAME_PREFIX);
// 设置拒绝策略
// 可选策略:
// - AbortPolicy: 抛出异常(默认)
// - CallerRunsPolicy: 由调用线程执行任务(防止系统崩溃,但会降低响应)
// - DiscardPolicy: 直接丢弃任务
// - DiscardOldestPolicy: 丢弃队列中最老的任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 初始化线程池
executor.initialize();
return executor;
}
private static class TracingThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute(@NotNull Runnable task) {
super.execute(wrap(task, getTraceContext()));
}
@NotNull
@Override
public <T> Future<T> submit(@NotNull Callable<T> task) {
return super.submit(wrap(task, getTraceContext()));
}
@NotNull
@Override
public Future<?> submit(@NotNull Runnable task) {
return super.submit(wrap(task, getTraceContext()));
}
private Runnable wrap(Runnable runnable, Map<String, String> context) {
return () -> {
// 保存原始子线程 MDC上下文
Map<String, String> original = MDC.getCopyOfContextMap();
// 父线程存在MDC上下文 则使用父线程 否则由子线程新建
if (MapUtils.isNotEmpty(context)) {
MDC.setContextMap(context);
} else {
Map<String, String> contextMap = buildNewContext();
MDC.setContextMap(contextMap);
}
try {
runnable.run();
} catch (Exception e) {
log.error("Runnable执行错误", e);
} finally {
if (MapUtils.isNotEmpty(original)) {
MDC.setContextMap(original);
} else {
MDC.clear();
}
}
};
}
private <T> Callable<T> wrap(Callable<T> callable, Map<String, String> context) {
return () -> {
// 保存原始子线程 MDC上下文
Map<String, String> original = MDC.getCopyOfContextMap();
// 父线程存在MDC上下文 则使用父线程 否则由子线程新建
if (MapUtils.isNotEmpty(context)) {
MDC.setContextMap(context);
} else {
Map<String, String> contextMap = buildNewContext();
MDC.setContextMap(contextMap);
}
try {
return callable.call();
} catch (Exception e) {
log.error("Callable执行错误", e);
throw e;
} finally {
if (MapUtils.isNotEmpty(original)) {
MDC.setContextMap(original);
} else {
MDC.clear();
}
}
};
}
private Map<String, String> getTraceContext() {
return MDC.getCopyOfContextMap();
}
private Map<String, String> buildNewContext() {
Map<String, String> contextMap = new HashMap<>();
contextMap.put(TRACE_ID, UUID.fastUUID().toString(true));
return contextMap;
}
}
}
3、解释
1、AsyncConfig
此配置是为了结合@Async注解而编写
2、defaultExecutor
此bean已被Spring所管理,用于常规异步任务,可单独由@Autowired或@Resource使用。 可以维护多个类似的bean,用于隔离多个线程池资源,例如用于异步操作数据库等的线程池。 使用@Bean注解进行定义即可。
3、TRACE_ID
基于MDC增加了TRACE_ID,处理了父子线程间TRACE_ID的传递,方便链路追踪。因为目前项目也是存在TRACE_ID的,也是做一个兼容。
4、关于此线程池的说明
目前线程池已经足以应对一般的规模,CPU密集型的任务可用,使用较少的线程避免上下文切换。如果是IO密集型的,根据规模可自行定义,可以进行压测进行调整。进一步可使用动态线程池进行动态调整优化。