写在前面的话
该文是在读到的众多有关@Async自定义线程池的博文中分析最为详细准确的。
本文为转载文章,版权所有归文章原始作者,如有需要请从原文转载:
原文链接:https://www.kuangstudy.com/bbs/1407940516238090242
在 秋招冲刺班 的视频中,飞哥讲到 [@Async](https://github.com/Async "@Async")
注解默认情况下用的是SimpleAsyncTaskExecutor
线程池,并且提到日志中的 taskId 是一直增长的。抱着探索的精神展开了如下测试:
问题复现
- SpringBoot 版本 2.4.7
- 测试机配置:双核Intel Core i5
异步方法如下:
@Async
public void sendMsg() {
// todo :模拟耗时5秒
try {
Thread.sleep(5000);
log.info("---------------发送消息--------");
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 添加积分,用异步进行处理和标记
@Async
public void addScore() {
// todo :模拟耗时5秒
try {
Thread.sleep(5000);
log.info("---------------处理积分--------");
} catch (Exception ex) {
ex.printStackTrace();
}
}
启动项目,多次调用这两个方法,得到如下日志结果。可以发现在当前环境下 task-${id}
这个 id 并不是一直增长的,而是一直在复用 1-8。这个时候可能就会有的小伙伴们会比较好奇,默认的不是 SimpleAsyncTaskExecutor
吗?为什么从日志打印的效果上看像是一直在复用 8 个线程,难道用的是 ThreadPoolTaskExecutor
?
验证猜想
为了验证我们的猜想,决定到源码里面去看一看,到底是用的是什么 TaskExecutor
。
首先找到了 @Async 的拦截器类org.springframework.aop.interceptor.AsyncExecutionInterceptor
// 当执行 @Async 修饰的异步方法时,就会进入到这个方法中
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);
final Method userDeclaredMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
// 在这里得到了方法的执行器
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
if (executor == null) {
throw new IllegalStateException(
"No executor specified and no default executor set on AsyncExecutionInterceptor either");
}
// ...
return doSubmit(task, executor, invocation.getMethod().getReturnType());
}
// AsyncExecutionInterceptor 父类中的方法,可以得到异步方法对应的执行器
protected AsyncTaskExecutor determineAsyncExecutor(Method method) {
// executors 是一个缓存,只要执行过一次就会记录它要使用的 TaskExecutor,不需要每次执行都去寻找要使用的 `TaskExecutor`
AsyncTaskExecutor executor = this.executors.get(method);
if (executor == null) {
Executor targetExecutor;
// 如果通过 @Async("myExectuor") 指定了执行器 "myExectuor",就找指定的,如果没有就用默认的。
String qualifier = getExecutorQualifier(method);
if (StringUtils.hasLength(qualifier)) {
targetExecutor = findQualifiedExecutor(this.beanFactory, qualifier);
} else {
// 通过观察源码,可以发现 defaultExecutor 是通过 getDefaultExecutor 得到的
targetExecutor = this.defaultExecutor.get();
}
if (targetExecutor == null) {
return null;
}
executor = (targetExecutor instanceof AsyncListenableTaskExecutor ?
(AsyncListenableTaskExecutor) targetExecutor : new TaskExecutorAdapter(targetExecutor));
this.executors.put(method, executor);
}
return executor;
}
// AsyncExecutionInterceptor 类中的 getDefaultExecutor
// !!!!!!!!!!!!!重点!!!!!!!!!!!!!
@Override
@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
// 调用父类 super.getDefaultExecutor 得到 Executor
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
// 如果没有得到默认的 Executor,则选用 SimpleAsyncTaskExecutor
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
// 父类中的 getDefaultExecutor
// 会去 Spring 的容器中找有没有 TaskExecutor 或名称为 'taskExecutor' 为 Executor 的
@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
return beanFactory.getBean(TaskExecutor.class);
} catch (NoUniqueBeanDefinitionException ex) {
return beanFactory.getBean("taskExecutor", Executor.class);
} catch (NoSuchBeanDefinitionException ex) {
return beanFactory.getBean("taskExecutor", Executor.class);
}
}
return null;
}
既然知道了在哪里记录了异步方法使用的什么 Executor
,我们可以通过 DEBUG 模式去查看。
但是我们通过 DEBUG 查看 executors
缓存却发现在当前版本中的 SpringBoot @Async 默认是用的是居然是 ThreadPoolTaskExecutor
。
小结:在这里我简单总结一下,在 SpringFramework 中,如果没有自定义的 TaskExecutor 或名称为 ‘taskExecutor’ 的 Bean 对象,那么就会使用 SimpleAsyncTaskExecutor
。所以在当前版本 SpringFramework 中 @Async 默认是用的是 SimpleAsyncTaskExecutor。
但是,从 DEBUG 模式中我们能发现,Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
这里居然取到了 Executor
,并且还是 ThreadPoolTaskExecutor
,那么我们就可以想到是不是有谁帮我们创建了一个 ThreadPoolTaskExecutor
的 Bean 对象呢?
为什么容器中会有 ThreadPoolTaskExecutor 的 Bean 对象
通过上面的实现结果和问题,我联想到了 spring-boot-autoconfigure
这个包,因为这个包会自动帮我们生成一些需要的 Bean 对象。最终成功在 org.springframework.boot.autoconfigure.task
下找到了如下类:
@ConditionalOnClass(ThreadPoolTaskExecutor.class)
@Configuration
@EnableConfigurationProperties(TaskExecutionProperties.class)
public class TaskExecutionAutoConfiguration {
public static final String APPLICATION_TASK_EXECUTOR_BEAN_NAME = "applicationTaskExecutor";
private final TaskExecutionProperties properties;
private final ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers;
private final ObjectProvider<TaskDecorator> taskDecorator;
public TaskExecutionAutoConfiguration(TaskExecutionProperties properties,
ObjectProvider<TaskExecutorCustomizer> taskExecutorCustomizers,
ObjectProvider<TaskDecorator> taskDecorator) {
this.properties = properties;
this.taskExecutorCustomizers = taskExecutorCustomizers;
this.taskDecorator = taskDecorator;
}
@Bean
@ConditionalOnMissingBean
public TaskExecutorBuilder taskExecutorBuilder() {
TaskExecutionProperties.Pool pool = this.properties.getPool();
TaskExecutorBuilder builder = new TaskExecutorBuilder();
builder = builder.queueCapacity(pool.getQueueCapacity());
builder = builder.corePoolSize(pool.getCoreSize());
builder = builder.maxPoolSize(pool.getMaxSize());
builder = builder.allowCoreThreadTimeOut(pool.isAllowCoreThreadTimeout());
builder = builder.keepAlive(pool.getKeepAlive());
builder = builder.threadNamePrefix(this.properties.getThreadNamePrefix());
builder = builder.customizers(this.taskExecutorCustomizers);
builder = builder.taskDecorator(this.taskDecorator.getIfUnique());
return builder;
}
// 只要没有 Executor 的 Bean 对象,那么就会帮你生成一个 ThreadPoolTaskExecutor 的 Bean 对象
@Lazy
@Bean(name = APPLICATION_TASK_EXECUTOR_BEAN_NAME)
@ConditionalOnMissingBean(Executor.class)
public ThreadPoolTaskExecutor applicationTaskExecutor(TaskExecutorBuilder builder) {
return builder.build();
}
}
小结:由于 spring-boot-autoconfigure
是 SpringBoot 一个重要的依赖,所以只要是 SpringBoot 项目就一定会依赖它,可以断定 ThreadPoolTaskExecutor
是 SpringBoot 项目中 Executor
的默认 Bean 对象。而 [@Async](https://github.com/Async "@Async")
在选择执行器的时候会先去 IOC 容器中先找是否有 TaskExecutor
的 Bean对象,所以在当前版本 SpringBoot 中,@Async 的默认 TaskExecutor 是 ThreadPoolTaskExecutor。
验证飞哥的说法
既然飞哥说默认的 SimpleAsyncTaskExecutor
,我选择相信飞哥,因为可能以前的版本的 SpringBoot 并不会默认帮我们创建一个 ThreadPoolTaskExecutor
的 Bean 对象。
查看源码,我找到了一个关键的信息。这说明 TaskExecutionAutoConfiguration
是从 2.1.0 版本才开始有的。
那么我将版本换到 2.1.0 之前再试试。在这里我选用了 2.1.0 的上一个版本 2.0.9 (完整的应该是 2.0.9.RELEASE)。
通过查看 spring-boot-autoconfigure-2.0.9
包,并没有找到 TaskExecutionAutoConfiguration
这个类。但是还需要通过代码的执行效果来验证。
验证 task-id 是不是一直增加
验证是由于从 Spring 容器中拿不到,然后默认使用 SimpleAsyncTaskExecutor
注:验证时,一定要是第一次调用异步方法,要不然就会走缓存。
验证缓存中存是的 SimpleAsyncTaskExecutor
完善飞哥的说法
总结:在 SpringBoot 2.0.9 版本及以前,@Async 默认使用的是 SimpleAsyncTaskExecutor
;从 2.1.0 开始到当前最新的 2.5.1,@Async 默认使用的是 ThreadPoolTaskExecutor
。
版权声明
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明,KuangStudy,以学为伴,一生相伴!
原文链接:https://www.kuangstudy.com/bbs/1407940516238090242