前言
@Async 注解是 SpringBoot 提供的一种非常简单的异步处理方式。因为简单,所以会忽略到一些使用细节。今天我们重新认识一下@Async 将又不是不能用变为更合理的用.
简单应用
当前 Spring boot 版本 2.7.21
# 启动类添加 @EnableAsync 注解,以开启异步处理能力
package io.github.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@EnableAsync
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
# 调用方法添加 @ Async 注解实现异步
package io.github.test.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TestController {
@Async
@RequestMapping("test")
public void test() {
log.info("处理耗时任务test......结束" + Thread.currentThread().getName());
}
}
# 执行结果分析
并发200压测后观察日志. 发现来回就9个线程再跑. task-1....task-9.可我们并没有配置线程池?为什么日志表现是池化调用的样子? 接着往下看...
Spring Boot 内置线程池 ThreadPoolTaskExecutor
debug 发现 @Async 底层执行逻辑是先在 BeanFactory 中寻找 TaskExecutor 线程池,找到就直接用. 找不到就创建新线程(SimpleAsyncTaskExecutor).
@Nullable
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
// 1. 先通过class类型寻找
return (Executor)beanFactory.getBean(TaskExecutor.class);
} catch (NoUniqueBeanDefinitionException ex) {
this.logger.debug("Could not find unique TaskExecutor bean. Continuing search for an Executor bean named 'taskExecutor'", ex);
try {
// 2.找不到再通过bean名称来找
return (Executor)beanFactory.getBean("taskExecutor", Executor.class);
} catch (NoSuchBeanDefinitionException var4) {
if (this.logger.isInfoEnabled()) {
// 注意这个提醒,注意这个提醒,注意这个提醒,最后会提到
this.logger.info("More than one TaskExecutor bean found within the context, and none is named 'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly as an alias) in order to use it for async processing: " + ex.getBeanNamesFound());
}
}
} catch (NoSuchBeanDefinitionException ex) {
this.logger.debug("Could not find default TaskExecutor bean. Continuing search for an Executor bean named 'taskExecutor'", ex);
try {
return (Executor)beanFactory.getBean("taskExecutor", Executor.class);
} catch (NoSuchBeanDefinitionException var5) {
this.logger.info("No task executor bean found for async processing: no bean of type TaskExecutor and no bean named 'taskExecutor' either");
}
}
}
return null;
}
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
// 3. 都找不到池才会创建线程
return (Executor)(defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
那已存在的 taskExecutor 怎么来的?真相就在 TaskExecutionAutoConfiguration 配置类中. SpringBoot 大哥初始化时就提前造好了一个叫 taskExecutor 的线程池.
结论
对于 SpringBoot 项目来说,直接使用 @Async 并不会每次都创建新线程来执行。而是交给内置的 TaskExecutor 线程池来执行调度。
为什么我使用 @Async 就每次都创建新线程?就消耗我?
// 前提:项目中开发者众多,业务细分多,所以有定义了多个池
@Configuration
public class PoolConfig {
@Bean("myTaskScheduler")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("poolScheduler");
scheduler.setPoolSize(50);
return scheduler;
}
@Bean("asyncHelpsExecutor")
public Executor asyncHelpsExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(50);
executor.setMaxPoolSize(1000);
executor.setQueueCapacity(0);
executor.setThreadNamePrefix("helps-async-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
// 上边 getDefaultExecutor 方法中输出的此提醒:
25-05-26.15:42:23.796 [http-nio-9191-exec-1] INFO AnnotationAsyncExecutionInterceptor - More than one TaskExecutor bean found within the context, and none is named 'taskExecutor'. Mark one of them as primary or name it 'taskExecutor' (possibly as an alias) in order to use it for async processing: [mytaskScheduler, asyncHelpsExecutor]
// 执行日志
25-05-26.15:42:24.805 [SimpleAsyncTaskExecutor-1] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-1
25-05-26.15:42:24.968 [SimpleAsyncTaskExecutor-2] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-2
25-05-26.15:42:25.124 [SimpleAsyncTaskExecutor-3] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-3
25-05-26.15:42:25.275 [SimpleAsyncTaskExecutor-4] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-4
25-05-26.15:42:25.431 [SimpleAsyncTaskExecutor-5] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-5
25-05-26.15:42:25.601 [SimpleAsyncTaskExecutor-6] INFO TestController - 处理耗时任务test......结束SimpleAsyncTaskExecutor-6
为什么这里是 SimpleAsyncTaskExecutor 来创建新线程,而非默认线程池。这和之前结论不符!为什么这样?
原因就在日志中的一句话 : More than one TaskExecutor bean found within the context....
大致意思是找到了2个池子,不知如何是好.就都不选.
最终结论
当 @Async 处理异步时,要结合业务细分,建议定义专属线程池,然后@Async("指定专属线程池").避免节外生枝。
// 一定要指定呀
@Async("asyncHelpsExecutor")
@RequestMapping("test")
public void test() {
log.info("处理耗时任务test......结束" + Thread.currentThread().getName());
}