提高系統性能的必備技能:異步任務完全指南
環境:Spring5.3.23
本文將介紹Spring框架中的異步任務,闡述為什么要使用異步任務以及異步任務帶來的好處。通過對Spring異步任務的深入了解,我們將掌握如何在Spring應用程序中實現高效的異步處理,并利用異步任務提高應用程序的性能和響應能力。
1. 前言
為什么要使用異步任務?
在傳統的同步應用程序中,每個請求都需要等待處理完成后再返回結果。這種方式在處理耗時操作時會導致應用程序性能下降,響應時間增加。為了解決這個問題,異步任務應運而生。通過將耗時操作移至后臺執行,異步任務可以避免阻塞主線程,提高應用程序的并發能力和響應速度。
異步任務的好處:
提高性能:異步任務可以避免阻塞主線程,使得應用程序能夠同時處理多個請求,提高了系統的吞吐量和性能。
改善用戶體驗:由于異步任務無需等待耗時操作完成,因此可以快速返回結果給用戶。這對于改善用戶體驗非常有益,用戶可以在短暫的等待時間后獲得響應,而無需長時間等待。
高效利用資源:異步任務可以充分利用系統資源,例如CPU和內存。在多核CPU系統中,異步任務可以同時運行多個任務,提高了資源的利用率。
降低系統負載:通過將耗時操作移至后臺執行,異步任務可以減輕前臺系統的負載,使其專注于處理核心業務邏輯。
適應高并發場景:在面對大量并發請求時,異步任務能夠更好地應對負載壓力,保證系統的穩定性和可用性。
總之,Spring異步任務為我們提供了一種高效處理耗時操作的方法,通過提高性能、改善用戶體驗、高效利用資源、降低系統負載以及適應高并發場景等方面的優勢,幫助我們構建更加出色的應用程序。
2. 實戰代碼
為了演示的方便,所有示例代碼我都將在一個類中完成。
在項目中要使用異步任務非常的簡單,我們只需要通過一個注解開啟即可,剩下的就只需要在需要異步執行的方法上添加上@Async注解即可。示例代碼如下:
通過@EnableAsync開啟異步任務
// 該配置類就作用就是開啟異步任務的能力
@Configuration
@EnableAsync
static class AppConfig {
}測試使用的組件類
@Component
static class AsyncService {
// 我們只需要在我們的方法上添加@Async即可
// 這樣該方法的執行將會在另外的線程中執行
@Async
public void calc() {
System.out.printf("執行線程: %s - 開始執行%n", Thread.currentThread().getName()) ;
try {
// 模擬耗時的操作
TimeUnit.SECONDS.sleep(2) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("線程: %s - 執行完成%n", Thread.currentThread().getName()) ;
}
}測試代碼
try (GenericApplicationContext context = new GenericApplicationContext()) {
// 容器中注冊相關的Bean
context.registerBean(ConfigurationClassPostProcessor.class) ;
context.registerBean(AppConfig.class) ;
context.registerBean(AsyncService.class) ;
context.refresh() ;
// 從容器中獲取組件
AsyncService as = context.getBean(AsyncService.class) ;
// 下面調用3次任務
as.calc() ;
as.calc() ;
as.calc() ;
System.out.println("主線程結束...") ;
System.in.read() ;
}執行結果
主線程結束...
執行線程: SimpleAsyncTaskExecutor-1 - 開始執行
執行線程: SimpleAsyncTaskExecutor-2 - 開始執行
執行線程: SimpleAsyncTaskExecutor-3 - 開始執行
線程: SimpleAsyncTaskExecutor-2 - 執行完成
線程: SimpleAsyncTaskExecutor-1 - 執行完成
線程: SimpleAsyncTaskExecutor-3 - 執行完成主線程早早的執行完了,每次方法的調用都在不同的線程,與阻塞執行相比大大提高了系統的吞吐量。
使用就是這么簡單,但是我們還需要更加的深入了解這里異步執行的線程是什么樣的一個線程池?是否可以自定義自己的線程池?接下來就從這2個問題來更加的深入學習異步任務執行的原理。
3. 異步任務使用的線程池
在Spring中使用異步任務的底層原理主要是通過Spring AOP(面向切面編程)來實現的。AOP是一種編程思想,它通過在程序執行的關鍵點上添加橫切關注點,來提高代碼的復用性和可維護性。
在Spring異步任務中,AOP被用于攔截方法的執行,將耗時的任務放入線程池中執行,從而避免阻塞主線程。具體來說,Spring異步任務底層使用了Java的Future和Callable接口,以及線程池技術來實現異步執行。
首先,當我們在Spring中定義一個異步方法時,實際上該方法并不會立即執行,而是會被封裝為一個Callable對象。Callable接口與Runnable接口類似,但它可以返回結果,并可以拋出異常。
異步任務執行的核心處理器類是:AsyncAnnotationBeanPostProcessor
該處理器的創建是在@EnableAsync注解中的@Import導入的類
public class ProxyAsyncConfiguration extends AbstractAsyncConfiguration {
@Bean(name = TaskManagementConfigUtils.ASYNC_ANNOTATION_PROCESSOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public AsyncAnnotationBeanPostProcessor asyncAdvisor() {
AsyncAnnotationBeanPostProcessor bpp = new AsyncAnnotationBeanPostProcessor();
// 線程池是引用的父類中的成員
bpp.configure(this.executor, this.exceptionHandler);
return bpp;
}
}
// 父類AbstractAsyncConfiguration
public abstract class AbstractAsyncConfiguration implements ImportAware {
protected Supplier<Executor> executor;
// 這里的入參是我們可以自定義實現的地方,后面會講到
@Autowired
void setConfigurers(ObjectProvider<AsyncConfigurer> configurers) {
Supplier<AsyncConfigurer> configurer = SingletonSupplier.of(() -> {
List<AsyncConfigurer> candidates = configurers.stream().collect(Collectors.toList());
if (CollectionUtils.isEmpty(candidates)) {
return null;
}
// 如果系統中定義了多個AsyncConfigurer將會拋出異常
if (candidates.size() > 1) {
throw new IllegalStateException("Only one AsyncConfigurer may exist");
}
return candidates.get(0);
});
// 如果沒有自定義,則調用AsyncConfigurer#getAsyncExecutor,默認這個方法返回的是null
// 所以,在默認情況下,這里的executor還是為null
this.executor = adapt(configurer, AsyncConfigurer::getAsyncExecutor);
this.exceptionHandler = adapt(configurer, AsyncConfigurer::getAsyncUncaughtExceptionHandler);
}
}接著進入核心的處理器類AsyncAnnotationBeanPostProcessor 該類中現在設置的executor還是為null。
public class AsyncAnnotationBeanPostProcessor {
// 在示例化當前處理器過程中會執行setBeanFactory方法
// 該方法中會定義AOP的切面(低級切面)Advisor
public void setBeanFactory(BeanFactory beanFactory) {
super.setBeanFactory(beanFactory);
// 該構造方法中會構建相應的通知及切入點
AsyncAnnotationAdvisor advisor = new AsyncAnnotationAdvisor(this.executor, this.exceptionHandler);
}
}
// 切面
public class AsyncAnnotationAdvisor {
public AsyncAnnotationAdvisor(...) {
// 構建通知攔截器
this.advice = buildAdvice(executor, exceptionHandler);
this.pointcut = buildPointcut(asyncAnnotationTypes);
}
protected Advice buildAdvice() {
// 該攔截器說下繼承關系
// 1. AnnotationAsyncExecutionInterceptor繼承 AsyncExecutionInterceptor
// 2. AsyncExecutionInterceptor 繼承 AsyncExecutionAspectSupport
AnnotationAsyncExecutionInterceptor interceptor = new AnnotationAsyncExecutionInterceptor(null);
// 在該方法中進行初始化線程池
// 調用父類AsyncExecutionAspectSupport#configure方法
interceptor.configure(executor, exceptionHandler);
return interceptor;
}
}
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport {
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
// 先調用父類,默認情況下父類返回null,下面有分析
Executor defaultExecutor = super.getDefaultExecutor(beanFactory);
// 當為null,這里就創建默認的線程池SimpleAsyncTaskExecutor
// 這也就是上面的示例代碼中默認線程池名稱打印的是SimpleAsyncTaskExecutor-*
return (defaultExecutor != null ? defaultExecutor : new SimpleAsyncTaskExecutor());
}
}
public abstract class AsyncExecutionAspectSupport {
public void configure(@Nullable Supplier<Executor> defaultExecutor,
@Nullable Supplier<AsyncUncaughtExceptionHandler> exceptionHandler) {
// defaultExecutor為null,則會獲取系統默認的getDefaultExecutor
// getDefaultExecutor這里的方法被子類AsyncExecutionInterceptor重寫了
this.defaultExecutor = new SingletonSupplier<>(defaultExecutor, () -> getDefaultExecutor(this.beanFactory));
}
// 初始化系統默認的線程池
protected Executor getDefaultExecutor(@Nullable BeanFactory beanFactory) {
if (beanFactory != null) {
try {
// 從容器中查找TaskExcutor類型的Bean
return beanFactory.getBean(TaskExecutor.class);
} catch (NoUniqueBeanDefinitionException ex) {
try {
// 如果容器中有多個這種Bean,則在通過beanName獲取
// beanName = taskExecutor
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
} catch (NoSuchBeanDefinitionException ex) {
try {
// 如果指定beanName=taskExecutor類型為TaskExecutor的Bean
// 則在獲取beanName=taskExecutor類型為Executor類型的Bean
return beanFactory.getBean(DEFAULT_TASK_EXECUTOR_BEAN_NAME, Executor.class);
}
}
}
return null;
}
}分析到這,在我們當前的環境下是沒有TaskExecutor或Executor類型的Bean。所以程序這里最終返回還是null。那這個默認線程池是誰呢?繼續向下看
在上面的buildAdvice方法中構建攔截器AnnotationAsyncExecutionInterceptor該攔截器是執行的核心
public class AsyncExecutionInterceptor extends AsyncExecutionAspectSupport implements MethodInterceptor {
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 確定任務執行的線程池
AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
}
}到此分析完了Spring的異步任務執行使用線程池的情況。現總結下查找線程池的流程步驟:
- 容器中查找AsyncConfigurer
- 在1中沒有,則容器中查找TaskExecutor類型的Bean,如果正好有一個則使用,如果有多個則從容器中查找beanName=taskExecutor,類型為Executor,如果沒有則返回null。
- 在2中如果沒有TaskExecutor類型的Bean,則從容器中查找beanName=taskExecutor,類型為Executor,如果沒有則返回null。
- 到此都還是沒有,則直接創建SimpleAsyncTaskExecutor對象作為線程池。
4. 自定義線程池
通過上面的分析你應該知道了如何自定義線程池了。
自定義AsyncConfigurer
@Component
static class CustomAsyncConfigurer implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactory() {
private final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group = Thread.currentThread().getThreadGroup() ;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix = "pack-" + poolNumber.getAndIncrement() +"-thread-" ;
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}) ;
}
}在容器中注冊上面的bean后,執行結果如下:
主線程結束...
執行線程: pack-1-thread-1 - 開始執行
執行線程: pack-1-thread-2 - 開始執行
線程: pack-1-thread-2 - 執行完成
線程: pack-1-thread-1 - 執行完成
執行線程: pack-1-thread-2 - 開始執行
線程: pack-1-thread-2 - 執行完成自定義線程池生效了。
其它方式就不嘗試了。

























