背景
SpringBoot的@Async默认线程池是否会导致OOM问题?
代码:
@Component
public class AsyncService {
/**
* 异步方法
*/
@Async
public void test() {
System.out.println("AsyncService----Thread.currentThread().getName() = " + Thread.currentThread().getName());
}
}
测试类:
@Test
public void testAsync() {
for (int i = 0; i < 1000; i++) {
asyncService.test();
}
System.out.println("1.Thread.currentThread().getName() = " + Thread.currentThread().getName());
}
探究1
环境
- Spring Boot的版本: 2.0.9.RELEASE
- @EnableAsync开启异步
运行结果
从结果看,线程数在不断增加
原理探究
debug调试,在类AsyncExecutionAspectSupport中确定Executor bean
这里会获取默认的Executor,继续往里走:
这里会发现默认的线程池是SimpleAsyncTaskExecutor,执行方法的原理如下:
从这里可以看到,每一个任务就会创建一个线程,如果任务多了,结果可想而知肯定会出现OOM
改进
增加一个自定义的线程池,作为@Async的默认线程池,上代码:
// 这里做一个简单的测试,具体的可以通过配置来灵活设置这些值
@Configuration
public class MyExecutorConfig {
@Bean("myExecutor")
public Executor getMyExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(1000);
executor.setThreadNamePrefix("mytask-");
executor.initialize();
return executor;
}
}
异步方法作如下修改:@Async(“myExecutor”)
@Component
public class AsyncService {
@Async("myExecutor")
public void test() {
System.out.println("AsyncService----Thread.currentThread().getName() = " + Thread.currentThread().getName());
}
}
运行结果:
结果是我们预期的。
这里为什么加上我们自己定义的bean之后,就走我们自己定义的了呢。AnnotationAsyncExecutionInterceptor类处理的
@Override
@Nullable
protected String getExecutorQualifier(Method method) {
// Maintainer's note: changes made here should also be made in
// AnnotationAsyncExecutionAspect#getExecutorQualifier
Async async = AnnotatedElementUtils.findMergedAnnotation(method, Async.class);
if (async == null) {
async = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Async.class);
}
return (async != null ? async.value() : null);
}
探究2
环境:
- Spring Boot的版本: 2.1.2.RELEASE
- @EnableAsync开启异步
运行结果
从结果看,线程数控制在10以内
原理探究
debug调试,在类AsyncExecutionAspectSupport中确定Executor bean
这里会获取默认的Executor,继续往里走:
这里会发现默认的线程池是ThreadPoolTaskExecutor,不再是SimpleAsyncTaskExecutor了,这里可以看出spring对这块存在的问题进行了改进,不再对每个任务再去创建一个线程
ThreadPoolTaskExecutor是如何注入的呢?
spring自2.1.0版本加入了一个自动配置类TaskExecutionAutoConfiguration,
@Lazy
@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
此方法即是ThreadPoolTaskExecutor bean的定义
总结
1. Spring Boot在2.1.0版本之前, 使用@Async 在不指定 Executor时 会默认使用 SimpleAsyncTaskExecutor线程池,任务量大会导致OOM;在指定Executor后可以避免此问题
2. Spring Boot在2.1.0版本之后, 使用@Async 会默认使用ThreadPoolTaskExecutor,避免OOM
3. @Async是可以使用的,使用时须慎重,最好指定线程池!!!