引言
在日常業(yè)務(wù)開(kāi)發(fā)中,異步編程已成為應(yīng)對(duì)并發(fā)挑戰(zhàn)和提升應(yīng)用程序性能的關(guān)鍵策略。傳統(tǒng)的同步編程方式,由于會(huì)阻礙主線程執(zhí)行后續(xù)任務(wù)直至程序代碼執(zhí)行結(jié)束,不可避免地降低了程序整體效率與響應(yīng)速度。因此,為克服這一瓶頸,開(kāi)發(fā)者廣泛采用異步編程技術(shù),將那些可能阻塞的長(zhǎng)時(shí)間運(yùn)行任務(wù)委派至后臺(tái)線程處理,從而確保主線程始終保持高效和靈敏的響應(yīng)能力。
而SpringBoot
作為一款廣受歡迎的應(yīng)用開(kāi)發(fā)框架,極大地簡(jiǎn)化了異步編程實(shí)踐。其中,@Async
注解是SpringBoot
為實(shí)現(xiàn)異步編程提供的便捷工具之一。通過(guò)巧妙地應(yīng)用@Async
注解,開(kāi)發(fā)者能夠無(wú)縫地將方法調(diào)用轉(zhuǎn)化為異步執(zhí)行模式,進(jìn)而增強(qiáng)系統(tǒng)的并發(fā)性能表現(xiàn)。
本文將深度剖析SpringBoot
中的@Async
注解,包括其內(nèi)在原理、具體使用方法以及相關(guān)注意事項(xiàng)。我們將深入探討@Async
的工作機(jī)制,展示如何在實(shí)際的SpringBoot
項(xiàng)目中有效運(yùn)用該注解。
@Async的原理
在SpringBoot
中,@Async
注解的實(shí)現(xiàn)原理基于Spring
框架的AOP
和任務(wù)執(zhí)行器(Task Executor
)機(jī)制。
@Async的啟用
開(kāi)啟對(duì)異步方法的支持需要在配置類(lèi)上添加@EnableAsync
注解,然后就可以激活了一個(gè)Bean
后處理器:AsyncConfigurationSelector
,它負(fù)責(zé)自動(dòng)配置AsyncConfigurer
,為異步方法提供所需的線程池。
而AsyncConfigurationSelector
中默認(rèn)使用PROXY
的代理,即使用ProxyAsyncConfiguration
,而ProxyAsyncConfiguration
是用于配置Spring異步方法的代理模式的配置類(lèi)。
當(dāng)然我們還可以指定使用另外一個(gè)代理模式:
AdviceMode.ASPECTJ
,以便使用AspectJ來(lái)進(jìn)行更高級(jí)的攔截和處理。
它繼承至AbstractAsyncConfiguration
,在AbstractAsyncConfiguration
中配置AsyncConfigurer
。setConfigurers
方法用于設(shè)置異步任務(wù)執(zhí)行器和異常處理器。
AsyncConfigurer
中提供了一種便捷的方式來(lái)配置異步方法的執(zhí)行器(AsyncTaskExecutor
)。通過(guò)實(shí)現(xiàn)AsyncConfigurer
接口,可以自定義異步方法的執(zhí)行策略、線程池等配置信息。默認(rèn)情況下Spring
會(huì)先搜索TaskExecutor
類(lèi)型的bean
或者名字為taskExecutor
的Executor
類(lèi)型的bean
,都不存在使用SimpleAsyncTaskExecutor
執(zhí)行器。
public interface AsyncConfigurer {
/*
* 該方法用于獲取一個(gè)AsyncTaskExecutor對(duì)象,用于執(zhí)行異步方法。
* 可以在這個(gè)方法中創(chuàng)建并配置自定義的AsyncTaskExecutor,例如ThreadPoolTaskExecutor或SimpleAsyncTaskExecutor等。
*/
@Nullable
default Executor getAsyncExecutor() {
return null;
}
/*
* 該方法用于獲取一個(gè)AsyncUncaughtExceptionHandler對(duì)象,用于處理異步方法執(zhí)行中未捕獲的異常。如果異步方法執(zhí)行過(guò)程中出現(xiàn)異常而沒(méi)有被捕獲,Spring會(huì)調(diào)用這個(gè)方法來(lái)處理異常。
* 可以在這個(gè)方法中返回自定義的AsyncUncaughtExceptionHandler實(shí)現(xiàn),以實(shí)現(xiàn)對(duì)異步方法異常的處理邏輯。
*/
@Nullable
default AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
}
同時(shí)ProxyAsyncConfiguration
中的AsyncAnnotationBeanPostProcessor
會(huì)掃描應(yīng)用上下文中的所有Bean
,檢查它們的方法是否標(biāo)記了@Async
注解。對(duì)于標(biāo)記了@Async
注解的方法,AsyncAnnotationBeanPostProcessor
會(huì)創(chuàng)建一個(gè)代理對(duì)象,用于在調(diào)用該方法時(shí)啟動(dòng)一個(gè)新的線程或使用線程池執(zhí)行該方法。這樣就實(shí)現(xiàn)了異步執(zhí)行的功能。同時(shí)它還負(fù)責(zé)處理@Async
注解中的其他屬性,例如設(shè)置異步方法的執(zhí)行超時(shí)時(shí)間、指定線程池名稱等。
異步方法注解與代理
當(dāng)服務(wù)類(lèi)的方法被@Async
注解修飾時(shí),Spring AOP
會(huì)檢測(cè)到這個(gè)注解,并利用動(dòng)態(tài)代理技術(shù)為該類(lèi)創(chuàng)建一個(gè)代理對(duì)象。其他組件通過(guò)Spring
容器調(diào)用帶有@Async
注解的方法時(shí),實(shí)際上是調(diào)用了代理對(duì)象的方法。
一個(gè)帶有@Async
注解的方法被調(diào)用時(shí),Spring AOP
會(huì)攔截這個(gè)方法調(diào)用。此時(shí)就會(huì)觸發(fā)處理異步調(diào)用的核心攔截器:AsyncExecutionInterceptor
。它的主要任務(wù)是將被@Async
修飾的方法封裝成一個(gè)Runnable
或者Callable
任務(wù),并將其提交給TaskExecutor
管理的線程池去執(zhí)行。這個(gè)過(guò)程確保了異步方法的執(zhí)行不會(huì)阻塞調(diào)用者線程。
TaskExecutor與線程池
TaskExecutor
是一個(gè)接口,定義了如何執(zhí)行Runnable
或Callable
任務(wù)。SpringBoot
提供了多種實(shí)現(xiàn),如SimpleAsyncTaskExecutor
、ThreadPoolTaskExecutor
等。通常我們會(huì)自定義一個(gè)ThreadPoolTaskExecutor
以滿足特定需求,比如設(shè)置核心線程數(shù)、最大線程數(shù)、隊(duì)列大小等參數(shù),以確保異步任務(wù)能夠高效并發(fā)執(zhí)行。AsyncExecutionInterceptor
將異步方法封裝的任務(wù)提交給配置好的TaskExecutor
管理的線程池執(zhí)行。
異步方法執(zhí)行與結(jié)果返回
異步方法的實(shí)際執(zhí)行在獨(dú)立的線程中進(jìn)行,不阻塞調(diào)用者線程。異步方法的返回類(lèi)型可以是voi 或者具有返回值,如果異步方法有返回值,那么返回類(lèi)型通常應(yīng)該是java.util.concurrent.Future
,這樣調(diào)用者可以通過(guò)Future
對(duì)象來(lái)檢查異步任務(wù)是否完成以及獲取最終的結(jié)果。
@Async使用
在SpringBoot
中,使用@Async
注解可以輕松地將方法標(biāo)記為異步執(zhí)行。下面來(lái)看一下如何在Spring Boot
項(xiàng)目中正確地使用@Async
注解,包括配置方法和注意事項(xiàng)。
在方法上添加@Async注解
要使用@Async注解,首先需要在要異步執(zhí)行的方法上添加該注解。這樣Spring就會(huì)在調(diào)用這個(gè)方法時(shí)將其封裝為一個(gè)異步任務(wù),并交給線程池執(zhí)行。
@Service
public class AsyncTaskService {
/**
* 通過(guò)@Async 注解表明該方法是個(gè)異步方法,
* @param i
*/
@Async
public void executeAsyncTask(Integer i) {
System.out.println(Thread.currentThread().getName()+" 執(zhí)行異步任務(wù):" + i);
}
}
啟用異步功能
在SpringBoot
應(yīng)用中,需要在配置類(lèi)上添加@EnableAsync
注解來(lái)啟用對(duì)異步方法的支持。
@EnableAsync
@SpringBootApplication
public class SpringBootBaseApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootBaseApplication.class, args);
}
}
配置線程池
默認(rèn)情況下,SpringBoot
會(huì)使用一個(gè)默認(rèn)的線程池來(lái)執(zhí)行異步任務(wù)(SimpleAsyncTaskExecutor
)。但是,為了更好地控制線程池的行為,我們可以自定義ThreadPoolTaskExecutor
,并通過(guò)AsyncConfigurer
進(jìn)行配置。
@Configuration
public class TaskExecutorConfig implements AsyncConfigurer{
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setQueueCapacity(25);
executor.setMaxPoolSize(10);
executor.setThreadNamePrefix("MyAsyncThread-");
executor.initialize();
return executor;
}
}
測(cè)試
@SpringBootTest
public class SpringBootBaseApplicationTests {
@Autowired
private AsyncTaskService asyncTaskService;
@Test
public void asyncTest() {
for (int i = 0; i < 10; i++) {
asyncTaskService.executeAsyncTask(i);
}
}
}
輸出結(jié)果如下:
注意事項(xiàng)
-
@Async
必須配合@EnableAsync
注解一起使用。兩者缺一不可。 -
異步方法必須定義在
Spring Bean
中,因?yàn)?code>Spring AOP是基于代理對(duì)象來(lái)實(shí)現(xiàn)的。假如我們把AsyncTaskService
類(lèi)中的@Service
去掉。就不創(chuàng)建Bean。然后測(cè)試代碼中修改為如下:
@Test
public void asyncTest() {
AsyncTaskService asyncTaskService = new AsyncTaskService();
for (int i = 0; i < 10; i++) {
asyncTaskService.executeAsyncTask(i);
}
}
執(zhí)行結(jié)果如下:
都是主線程同步方法。
- 異步方法不能定義為private或static,因?yàn)镾pring AOP無(wú)法攔截這些方法。我們修改
AsyncTaskService
類(lèi)中的方法修改為private
或者static
,則會(huì)發(fā)生編譯錯(cuò)誤:
- 異步方法內(nèi)部的調(diào)用不能使用this關(guān)鍵字,因?yàn)閠his關(guān)鍵字是代理對(duì)象的引用,會(huì)導(dǎo)致異步調(diào)用失效。
@Service
public class AsyncTaskService {
@Async
public void asyncMethod() {
System.out.println("Async method executed in thread: " + Thread.currentThread().getName());
}
public void callAsyncMethod() {
// 在同一個(gè)類(lèi)中直接調(diào)用異步方法
this.asyncMethod(); // 這里調(diào)用不會(huì)觸發(fā)異步執(zhí)行
System.out.println("callAsyncMethod executed in thread: " + Thread.currentThread().getName());
}
}
- 被
@Async
注解修飾的方法不能直接被同一個(gè)類(lèi)中的其他方法調(diào)用。原因是Spring
會(huì)在運(yùn)行時(shí)生成一個(gè)代理類(lèi),調(diào)用異步方法時(shí)實(shí)際上是調(diào)用這個(gè)代理類(lèi)的方法。因此,如果在同一個(gè)類(lèi)中直接調(diào)用異步方法,@Async
注解將不會(huì)生效,因?yàn)檫@樣調(diào)用會(huì)繞過(guò)代理對(duì)象,導(dǎo)致異步執(zhí)行失效。
@Service
public class AsyncTaskService {
@Async
public void asyncMethod() {
System.out.println("Async method executed in thread: " + Thread.currentThread().getName());
}
public void callAsyncMethod() {
// 在同一個(gè)類(lèi)中直接調(diào)用異步方法
asyncMethod(); // 這里調(diào)用不會(huì)觸發(fā)異步執(zhí)行
System.out.println("callAsyncMethod executed in thread: " + Thread.currentThread().getName());
}
}
測(cè)試代碼:
@SpringBootTest
public class SpringBootBaseApplicationTests {
@Autowired
private AsyncTaskService asyncTaskService;
@Test
public void asyncTest2(){
asyncTaskService.callAsyncMethod();
}
}
執(zhí)行結(jié)果如下:
- 不同的異步方法間不要相互調(diào)用
異步方法間的相互調(diào)用會(huì)顯著增加代碼的復(fù)雜性層級(jí),由于異步執(zhí)行的本質(zhì)在于即時(shí)返回并延遲完成任務(wù),因此,嵌套或遞歸式的異步調(diào)用容易導(dǎo)致邏輯難以梳理和維護(hù),特別是在涉及多異步操作狀態(tài)追蹤、順序控制及依賴關(guān)系管理時(shí)尤為突出。
當(dāng)異步方法內(nèi)部進(jìn)一步調(diào)用其他異步方法,并且牽涉到同步資源如鎖、信號(hào)量等時(shí),極易引發(fā)死鎖問(wèn)題。例如,一個(gè)線程在等待自身啟動(dòng)的另一個(gè)異步任務(wù)結(jié)果的同時(shí),該任務(wù)卻嘗試獲取第一個(gè)線程所持有的鎖,如此循環(huán)等待,形成無(wú)法解開(kāi)的死鎖。
無(wú)節(jié)制地在異步方法內(nèi)部啟動(dòng)新的異步任務(wù),特別是在高并發(fā)場(chǎng)景下,可能導(dǎo)致線程池資源迅速耗盡,使得系統(tǒng)喪失處理更多請(qǐng)求的能力。此外,直接的異步方法調(diào)用還增加了錯(cuò)誤處理與日志記錄的難度,特別是遇到異常情況時(shí),往往難以追溯原始調(diào)用鏈路以精準(zhǔn)定位問(wèn)題源頭。
若需要確保異步方法按照特定順序執(zhí)行,直接調(diào)用會(huì)導(dǎo)致邏輯混亂不清。為解決這一問(wèn)題,通常推薦采用回調(diào)機(jī)制、Future/CompletionStage鏈?zhǔn)骄幊?、響?yīng)式編程模型(如RxJava、Project Reactor)等方式來(lái)確保有序執(zhí)行并降低耦合度。
同時(shí),頻繁且低延遲的任務(wù)間直接互相調(diào)用可能會(huì)引入額外的上下文切換開(kāi)銷(xiāo),從而對(duì)系統(tǒng)的整體性能造成潛在負(fù)面影響。
- 合理配置線程池
Spring Boot
默認(rèn)提供的線程池配置可能無(wú)法充分滿足特定應(yīng)用在復(fù)雜多變生產(chǎn)環(huán)境下的需求,例如其預(yù)設(shè)的線程數(shù)、隊(duì)列大小和拒絕策略等參數(shù)可能不盡合理。為確保資源的有效管理和精細(xì)控制,我們可以通過(guò)自定義線程池來(lái)靈活設(shè)定核心線程數(shù)、最大線程數(shù)、線程空閑超時(shí)時(shí)間、任務(wù)等待隊(duì)列容量以及飽和策略(如任務(wù)拒絕策略)等關(guān)鍵屬性,從而適應(yīng)不同業(yè)務(wù)場(chǎng)景對(duì)并發(fā)執(zhí)行任務(wù)數(shù)量及資源消耗的精準(zhǔn)調(diào)控。
另外,不同類(lèi)型異步任務(wù)具有不同的執(zhí)行特性:有的任務(wù)耗時(shí)較長(zhǎng),而有的則短促且頻繁。針對(duì)這種情況,為各類(lèi)任務(wù)配置獨(dú)立的線程池有助于實(shí)現(xiàn)更好的資源隔離,避免任務(wù)間的相互影響,進(jìn)而保障系統(tǒng)的穩(wěn)定性和響應(yīng)速度。同時(shí),為了滿足特定的安全規(guī)范或性能要求,自定義線程池還可以支持諸如設(shè)置守護(hù)線程、優(yōu)先級(jí)、線程命名格式化等功能。
更重要的是,自定義線程池有利于系統(tǒng)內(nèi)部執(zhí)行狀態(tài)的深度監(jiān)控與問(wèn)題診斷。通過(guò)制定合理的命名規(guī)則、詳盡的日志記錄以及精確的metrics統(tǒng)計(jì)分析,我們可以清晰洞察每個(gè)線程池的工作狀況,及時(shí)發(fā)現(xiàn)并優(yōu)化潛在的性能瓶頸。
如果不進(jìn)行自定義線程池配置,僅依賴于默認(rèn)或簡(jiǎn)化的線程池實(shí)現(xiàn),在面對(duì)大量涌入的任務(wù)時(shí),可能會(huì)因線程資源耗盡導(dǎo)致整個(gè)系統(tǒng)響應(yīng)能力和可用性受損。因此,采用合理配置的自定義線程池能夠在高負(fù)載環(huán)境下有效防范此類(lèi)風(fēng)險(xiǎn),有力支撐系統(tǒng)的穩(wěn)健運(yùn)行。
@Configuration
public class TaskExecutorConfig implements AsyncConfigurer{
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心線程數(shù)
executor.setCorePoolSize(5);
// 設(shè)置隊(duì)列容量
executor.setQueueCapacity(25);
// 最大線程數(shù)
executor.setMaxPoolSize(10);
// 自定義線程名稱前綴
executor.setThreadNamePrefix("MyAsyncThread-");
executor.initialize();
return executor;
}
}
關(guān)于自定義線程池的參數(shù)講解請(qǐng)參考我這篇文章:重溫Java基礎(chǔ)(二)之Java線程池最全詳解
- 異常處理:
異步方法內(nèi)部的異常通常不會(huì)被調(diào)用方捕獲到,因此需要在異步方法內(nèi)部進(jìn)行異常處理,可以通過(guò)try-catch
塊:
@Async
public void asyncMethod() {
try {
// 異步操作代碼
} catch (Exception ex) {
log.error("Error occurred in async method", ex);
// 其他錯(cuò)誤處理邏輯
}
}
或者使用@Async
注解的exceptionHandler
屬性來(lái)處理異常并進(jìn)行適當(dāng)?shù)娜罩居涗浕蝈e(cuò)誤處理。
@Configuration
public class TaskExecutorConfig implements AsyncConfigurer{
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
}
// 自定義異常處理器
class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Uncaught exception in async method: " + method.getName(), ex);
// 其他錯(cuò)誤處理邏輯
}
}
- 與事務(wù)的交互
默認(rèn)情況下,當(dāng)我們?cè)赟pring應(yīng)用中使用@Async
注解標(biāo)記一個(gè)方法為異步執(zhí)行時(shí),這個(gè)方法將不會(huì)參與到其調(diào)用者所處的事務(wù)上下文中。這意味著,如果調(diào)用異步方法的方法在一個(gè)事務(wù)內(nèi)執(zhí)行,該事務(wù)將在調(diào)用異步方法后正常提交或回滾,但異步方法內(nèi)部的操作并不會(huì)受到這個(gè)事務(wù)的影響。
例如,若在同步方法中修改了數(shù)據(jù)庫(kù)記錄,并隨后調(diào)用了一個(gè)異步方法來(lái)更新其他相關(guān)的數(shù)據(jù),那么如果同步方法中的事務(wù)在調(diào)用異步方法后提交,而異步方法在執(zhí)行過(guò)程中拋出了異常導(dǎo)致更新失敗,這時(shí)第一部分已提交的數(shù)據(jù)和第二部分未成功更新的數(shù)據(jù)之間就會(huì)產(chǎn)生不一致的情況。
為了確保異步方法能夠正確地參與事務(wù)管理,可以通過(guò)設(shè)置@Async
注解的事務(wù)傳播行為屬性(@Transactional
的propagation
屬性值)來(lái)解決這個(gè)問(wèn)題。
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Async
public void asyncMethod() {
System.out.println("Async method executed in thread: " + Thread.currentThread().getName());
// 具體業(yè)務(wù)
}
這里通過(guò)設(shè)置Propagation.REQUIRES_NEW
,指示Spring在執(zhí)行異步方法時(shí)開(kāi)啟一個(gè)新的、與當(dāng)前事務(wù)無(wú)關(guān)的事務(wù)。這樣即使異步方法內(nèi)部發(fā)生異常,它自己的事務(wù)會(huì)獨(dú)立進(jìn)行提交或回滾,從而保證了數(shù)據(jù)的一致性。不過(guò)要注意的是,這種做法可能會(huì)增加系統(tǒng)資源消耗,因?yàn)槊看萎惒饺蝿?wù)都會(huì)創(chuàng)建新的事務(wù)上下文。
總結(jié)
通過(guò)本文的介紹,我們了解了SpringBoot
中@Async
注解的原理、使用方法以及需要注意的事項(xiàng)。
@Async
注解能夠?qū)⒎椒?biāo)記為異步執(zhí)行,利用了Spring
框架的AOP
和任務(wù)執(zhí)行器機(jī)制,使得異步方法能夠在后臺(tái)線程池中并發(fā)執(zhí)行,提高系統(tǒng)的并發(fā)能力和響應(yīng)性。
然而,在使用@Async
注解時(shí),需要注意避免異步方法之間相互調(diào)用,合理配置線程池,進(jìn)行異常處理,處理上下文丟失以及與事務(wù)的正確交互。這些注意事項(xiàng)能夠確保異步方法的可靠性和穩(wěn)定性,提高應(yīng)用程序的性能和可維護(hù)性。
總的來(lái)說(shuō),@Async
注解是SpringBoot
中用于實(shí)現(xiàn)異步方法的重要特性,能夠有效地提升應(yīng)用程序的性能和并發(fā)能力,但在使用時(shí)需要謹(jǐn)慎考慮其使用場(chǎng)景和注意事項(xiàng),以充分發(fā)揮其優(yōu)勢(shì)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-838486.html
本文已收錄于我的個(gè)人博客:碼農(nóng)Academy的博客,專注分享Java技術(shù)干貨,包括Java基礎(chǔ)、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構(gòu)設(shè)計(jì)、面試題、程序員攻略等文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-838486.html
到了這里,關(guān)于提升Spring Boot應(yīng)用性能的秘密武器:揭秘@Async注解的實(shí)用技巧的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!