前言
以前负责的一个项目,从单体架构往微服务架构迁移时,引入了Consul作为服务配置中心,然后导致所有的异步定时任务(@schedule+@Async)都不执行了;跟源码发现Consul作为服务配置中心时会在client端起一个定时任务线程池(核心线程数和最大线程数均为1)其伦村Consul Server中的服务配置;
由于@Async默认使用SpringBoot自带的线程池,而这个线程池已经被Consul创建,并且核心线程数和最大线程数都为1,就导致@Async一直拿不到可用的线程,进而所有的定时任务都没有执行;
当时的解决方案:
- 自定义线程池,@Async使用时指定线程池名称,执行异步任务。
自定义线程池
在@Configuration类中通过@Bean的方式注入一个ThreadPoolTaskExecutor
到Spring容器;
@Bean("myExecutor")
public ThreadPoolTaskExecutor mqInvokeAysncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 线程池中的线程名前缀
executor.setThreadNamePrefix("log-message-");
// 设置线程池关闭的时候需要等待所有任务都完成 才能销毁其他的Bean
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住
executor.setAwaitTerminationSeconds(120);
executor.setCorePoolSize(4);
executor.setMaxPoolSize(16);
// 任务阻塞队列的大小
executor.setQueueCapacity(20000);
return executor;
}
使用自定义线程池
1)@Async中使用
在使用的方法上面添加@Async(“自定义线程池类beanName”)注解;
public class AsyncTest {
// 指定使用哪个线程池,不指定则使用spring默认的线程池
@Async("myExecutor")
public void executeAsync() {
System.out.println("executeAsync");
}
}
2)CompletableFuture中使用
当使用CompletableFuture.runAsync()方法异步执行一系列任务时,可以在方法的第二个参数中指定用哪个线程池执行任务;
public class ImportOrderLogMsgProducer {
@Resource(name = "myExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
public void testAsync(LogMessage logMessage) {
CompletableFuture.runAsync(() -> {
try {
// do something
} catch (Exception e) {
log.error("... catch:{}", e.getMessage(), e);
}
}, threadPoolTaskExecutor);
}
}