淺析Spring中Async注解底層異步線程池原理
一、前言
開發(fā)中我們經(jīng)常會用到異步方法調(diào)用,具體到代碼層面,異步方法調(diào)用的實(shí)現(xiàn)方式有很多種,比如最原始的通過實(shí)現(xiàn)Runnable接口或者繼承Thread類創(chuàng)建異步線程,然后啟動異步線程;再如,可以直接用java.util.concurrent包提供的線程池相關(guān)API實(shí)現(xiàn)異步方法調(diào)用。
如果說可以用一行代碼快速實(shí)現(xiàn)異步方法調(diào)用,那是不是比上面方法香很多。
Spring提供了Async注解,就可以幫助我們一行代碼搞定異步方法調(diào)用。Async注解用起來是很爽,但是如果不對其底層實(shí)現(xiàn)做深入研究,難免有時(shí)候也會心生疑慮,甚至?xí)蚴褂貌划?dāng),遇見一些讓人摸不著頭腦的問題。
本文首先將對Async注解做簡單介紹,然后和大家分享一個(gè)我們項(xiàng)目中因Async注解使用不當(dāng)?shù)木€上問題,接著再深扒Spring源碼,對Async注解底層異步線程池的實(shí)現(xiàn)原理一探究竟。
二、Async注解簡介
Async注解定義源碼
圖片
從源碼可以看出@Async注解定義很簡單,只需要關(guān)注兩點(diǎn):
- Target({ElementType.TYPE, ElementType.METHOD})標(biāo)志Async注解可以作用在方法和類上,作用在類上時(shí),類的所有方法可以實(shí)現(xiàn)異步調(diào)用。
- String value( ) default ""是唯一字段屬性,用來指定異步線程池,且該字段有缺省值。
Async注解異步調(diào)用實(shí)現(xiàn)原理概述
在Spring框架中,Async注解的實(shí)現(xiàn)是通過AOP來實(shí)現(xiàn)的。具體來說,Async注解是由AsyncAnnotationAdvisor這個(gè)切面類來實(shí)現(xiàn)的。
AsyncAnnotationAdvisor類是Spring框架中用于處理Async注解的切面,它會在被Async注解標(biāo)識的方法被調(diào)用時(shí),創(chuàng)建一個(gè)異步代理對象來執(zhí)行方法。這個(gè)異步代理對象會在一個(gè)新的線程中調(diào)用被@Async注解標(biāo)識的方法,從而實(shí)現(xiàn)方法的異步執(zhí)行。
在AsyncAnnotationAdvisor中,會使用AsyncExecutionInterceptor來處理Async注解。AsyncExecutionInterceptor是實(shí)現(xiàn)了MethodInterceptor接口的類,用于攔截被Async注解標(biāo)識的方法的調(diào)用,并在一個(gè)新的線程中執(zhí)行這個(gè)方法。
通過AOP的方式實(shí)現(xiàn)Async注解的異步執(zhí)行,Spring框架可以在方法調(diào)用時(shí)動態(tài)地創(chuàng)建代理對象來實(shí)現(xiàn)異步執(zhí)行,而不需要在業(yè)務(wù)代碼中顯式地創(chuàng)建新線程。
總的來說,Async注解的實(shí)現(xiàn)是通過AOP機(jī)制來實(shí)現(xiàn)的,具體的切面類是AsyncAnnotationAdvisor,它利用AsyncExecutionInterceptor來處理被Async注解標(biāo)識的方法的調(diào)用,實(shí)現(xiàn)方法的異步執(zhí)行。
三、Async注解底層異步線程池原理探究
獲取Async注解線程池主流程解析
進(jìn)入到Spring源碼Async注解AOP切面實(shí)現(xiàn)部分,我們重點(diǎn)剖析異步調(diào)用實(shí)現(xiàn)中線程池是怎么處理的。下圖是org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke方法的實(shí)現(xiàn),可以看出是調(diào)用determineAsyncExecutor方法獲取異步線程池。
AsyncExecutionInterceptor#invoke
下圖是determineAsyncExecutor方法實(shí)現(xiàn):
圖片
圖片
左圖為AsyncExecutionInterceptor#determineAsyncExecutor,右圖為AsyncExecutionAspectSupport#getExecutorQualifier
從代碼實(shí)現(xiàn)中可以看到determineAsyncExecutor獲取線程池的大致流程:
determineAsyncExecutor獲取線程池流程
如果在使用Async注解時(shí)指定了自定義線程池比較好理解,如果使用Async注解時(shí)沒有指定自定義線程池,Spring是怎么處理默認(rèn)線程池呢?繼續(xù)深入源碼看看Spring提供的默認(rèn)線程池的實(shí)現(xiàn)。
Spring是怎么為Async注解提供默認(rèn)線程池的
Async注解默認(rèn)線程池有下面兩個(gè)方法實(shí)現(xiàn):
- org.springframework.aop.interceptor.AsyncExecutionInterceptor#getDefaultExecutor
- org.springframework.aop.interceptor.AsyncExecutionAspectSupport#getDefaultExecutor
AsyncExecutionInterceptor#getDefaultExecutor
可以看出AsyncExecutionInterceptor#getDefaultExecutor方法比較簡單:先嘗試調(diào)用父類AsyncExecutionAspectSupport#getDefaultExecutor方法獲取線程池,如果父類方法獲取不到線程池再用創(chuàng)建SimpleAsyncTaskExecutor對象作為Async的線程池返回。
AsyncExecutionAspectSupport#getDefaultExecutor
再來看父類AsyncExecutionAspectSupport#getDefaultExecutor方法的實(shí)現(xiàn),可以看到Spring根據(jù)類型從Spring容器中獲取TaskExecutor類的實(shí)例,先記住這個(gè)關(guān)鍵點(diǎn)。
我們知道,Spring根據(jù)類型獲取實(shí)例時(shí),如果spring容器中有且只有一個(gè)指定類型的實(shí)例對象,會直接返回,否則的話,會拋出NoUniqueBeanDefinitionException異?;蛘逳oSuchBeanDefinitionException異常。
但是,對于Executor類型,Spring容器卻“網(wǎng)開一面”,有一個(gè)特殊處理:當(dāng)從Spring容器中獲取Executor實(shí)例對象時(shí),如果滿足@ConditionalOnMissingBean(Executor.class)條件,Spring容器會自動裝載一個(gè)ThreadPoolTaskExecutor實(shí)例對象,而ThreadPoolTaskExecutor是TaskExecutor的實(shí)現(xiàn)類。
圖片
圖片
左圖為TaskExecutionAutoConfiguration,右圖為TaskExecutionProperties
從TaskExecutionProperties和TaskExecutionAutoConfiguration兩個(gè)配置類我們看到Spring自動裝載的ThreadPoolTaskExecutor線程池對象的參數(shù):核心線程數(shù)=8;最大線程數(shù)=Integer.MAX_VALUE;隊(duì)列大小=Integer.MAX_VALUE。
四、總結(jié)
現(xiàn)在Async注解線程池源碼已經(jīng)看的差不多了,下面這張圖是Spring處理Async異步線程池的流程:
Async異步線程池獲取流程
歸納一下:如果在使用Async注解時(shí)沒有指定自定義的線程池會出現(xiàn)以下幾種情況:
- 當(dāng)Spring容器中有且僅有一個(gè)TaskExecutor實(shí)例時(shí),Spring會用這個(gè)線程池來處理Async注解的異步任務(wù),這可能會踩坑,如果這個(gè)TaskExecutor實(shí)例是第三方j(luò)ar引入的,可能會出現(xiàn)很詭異的問題。
- Spring創(chuàng)建一個(gè)核心線程數(shù)=8、最大線程數(shù)=Integer.MAX_VALUE、隊(duì)列大小=Integer.MAX_VALUE的線程池來處理Async注解的異步任務(wù),這時(shí)候也可能會踩坑,由于線程池參數(shù)設(shè)置不合理,核心線程數(shù)=8,隊(duì)列大小過大,如果有大批量并發(fā)任務(wù),可能會出現(xiàn)OOM。
- Spring創(chuàng)建SimpleAsyncTaskExecutor實(shí)例來處理Async注解的異步任務(wù),SimpleAsyncTaskExecutor不是一個(gè)好的線程池實(shí)現(xiàn)類,SimpleAsyncTaskExecutor根據(jù)需要在當(dāng)前線程或者新線程中執(zhí)行異步任務(wù)。如果當(dāng)前線程已經(jīng)有空閑線程可用,任務(wù)將在當(dāng)前線程中執(zhí)行,否則將創(chuàng)建一個(gè)新線程來執(zhí)行任務(wù)。由于這個(gè)線程池沒有線程管理的能力,每次提交任務(wù)都實(shí)時(shí)創(chuàng)建新城,所以如果任務(wù)量大,會導(dǎo)致性能下降。