最近看到很一篇文章说SpringBoot项目中@Async默认线程池会导致OOM,因为我的项目中也用到@Async注解,所以赶紧看了一下,在网上搜索@Async导致OOM案例还是很多的,于是我我就研究了一下。
使用SpringBoot 2.0.9.RELEASE 创建演示工程,项目比较简单,使用@EnableAsync开启异步,在TaskService中@Async开启方法异步。
@SpringBootApplication
@EnableAsync
@RestController
public class AsyncDemoApplication {
@Resource
private TaskService taskService;
@GetMapping("/id")
public String test() {
taskService.deel();
return "ok";
}
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
@Component
@Slf4j
public class TaskService {
@Async
public void deel() {
log.info("Thread Name :{} ", Thread.currentThread().getName());
}
}
从压测结果上来看,刚一开始等待线程数就有3024条,日志中SimpleAsyncTaskExecutor-11922 编号已达到11922个了,看来确实和网上说的一样 @Async默认线程池是SimpleAsyncTaskExecutor,会每次创建一个线程去执行任务,任务量大了会产生OOM。
关键代码:
protected void doExecute(Runnable task) {
Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
thread.start();
}
在SimpleAsyncTaskExecutor的doExecute方法中每次会创建新线程来执行,所以导致瞬间等待线程数达到3024条,确实有点坑人。
升级后结果完全不同了,线程数量并没有上升,线程名也变成task-8 这样的了,看来是SpringBoot 优化掉了这个 SimpleAsyncTaskExecutor
在2.1.0之后的版本多了一个TaskExecutionAutoConfiguration,在项目缺少 Executor Bean的情况下注入了一个ThreadPoolTaskExecutor,作为@Async默认线程池。
@SpringBootApplication
@EnableAsync
@RestController
public class AsyncDemoApplication {
@Bean("fileTask")
public Executor fileTask() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("file-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Resource
private TaskService taskService;
@GetMapping("/id")
public String test() {
taskService.deel();
return "ok";
}
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
从测试结果来看是没有问题,使用项目中配置的fileTask
@SpringBootApplication
@EnableAsync
@RestController
public class AsyncDemoApplication {
@Bean("fileTask")
public Executor fileTask() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("file-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Bean("imgTask")
public Executor imgTask() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("img-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Resource
private TaskService taskService;
@GetMapping("/id")
public String test() {
taskService.deel();
return "ok";
}
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
如果项目中配置了多个Executor也会使用 SimpleAsyncTaskExecutor
原因是在AsyncExecutionInterceptor 中调用 getDefaultExecutor() 时beanFactory.getBean(TaskExecutor.class) 找到了两个bean所以 报了 NoUniqueBeanDefinitionException,导致没获取到Executor而使用了SimpleAsyncTaskExecutor,所以在fileTask 添加@Primary可解决多个bean问题。