Java Spring的@Async的使用及注意事項(xiàng)示例總結(jié)
1、概念和用途
@Async是 Spring 框架提供的一個(gè)注解,用于標(biāo)記一個(gè)方法,在一個(gè)單獨(dú)的線程中異步執(zhí)行。
這在處理一些耗時(shí)的操作(比如發(fā)送郵件、調(diào)用外部 API 等)時(shí)非常有用。
通過使用@Async,可以讓這些操作在后臺(tái)執(zhí)行,而不會(huì)阻塞主線程,從而提高應(yīng)用程序的性能和響應(yīng)速度。
例如,在一個(gè)Web應(yīng)用程序中,當(dāng)用戶提交一個(gè)訂單后,可能需要發(fā)送一封確認(rèn)郵件。如果使用同步方式,用戶必須等待郵件發(fā)送完成后才能得到訂單提交成功的響應(yīng)。而使用@Async,可以讓郵件發(fā)送操作在后臺(tái)線程中進(jìn)行,用戶幾乎可以立即得到訂單提交成功的響應(yīng)。
2、使用
2.1 啟用異步支持
首先,需要在 Spring 配置類上添加@EnableAsync注解來開啟異步方法執(zhí)行功能。這個(gè)注解會(huì)掃描帶有@Async標(biāo)記的方法,并為它們創(chuàng)建獨(dú)立的線程來執(zhí)行。
示例配置類如下:
@Configuration
@EnableAsync
public class AppConfig {
// 可以在這里進(jìn)行其他配置,如Bean定義等
}2.2 標(biāo)記異步方法
在需要異步執(zhí)行的方法上添加@Async注解。這個(gè)方法通常應(yīng)該返回void或者Future類型。
如果返回void,方法執(zhí)行完成后不會(huì)返回任何結(jié)果。如果返回Future,可以在之后獲取異步方法的執(zhí)行結(jié)果。
例如,下面是一個(gè)簡單的異步方法,它模擬了一個(gè)耗時(shí)的操作:
@Service
public class MyService {
@Async
public void doSomethingAsync() {
try {
// 模擬耗時(shí)操作,這里休眠3秒
Thread.sleep(30000);
System.out.println("異步方法執(zhí)行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}2.3 調(diào)用異步方法
可以在其他組件(如控制器、其他服務(wù)方法等)中調(diào)用這個(gè)異步方法。調(diào)用時(shí),方法會(huì)立即返回,而實(shí)際的操作會(huì)在后臺(tái)線程中執(zhí)行。
例如,在一個(gè) Spring MVC 控制器中調(diào)用上述異步方法:
@RestController
public class MyController {
@Autowired
private MyService myService;
@GetMapping("/async")
public String asyncEndpoint() {
myService.doSomethingAsync();
return "異步操作已啟動(dòng)";
}
}2.4 需要異步結(jié)果時(shí)
如果異步方法需要返回一個(gè)結(jié)果,可以將方法的返回類型定義為Future。Future接口是 Java 并發(fā)包中的一部分,用于表示一個(gè)異步計(jì)算的結(jié)果。
例如,修改前面的MyService中的方法如下:
@Async
public Future<String> doSomethingAsyncWithResult() {
try {
// 模擬耗時(shí)操作,這里休眠3秒
Thread.sleep(3000);
return new AsyncResult<>("異步方法執(zhí)行結(jié)果");
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}然后在調(diào)用這個(gè)方法的地方,可以通過Future的get方法來獲取結(jié)果:
@GetMapping("/async - result")
public String asyncResultEndpoint() {
try {
Future<String> futureResult = myService.doSomethingAsyncWithResult();
String result = futureResult.get();
return result;
} catch (Exception e) {
e.printStackTrace();
return "獲取結(jié)果出錯(cuò)";
}
}注意:get方法會(huì)阻塞當(dāng)前線程,直到異步方法執(zhí)行完成并返回結(jié)果
3、注意事項(xiàng)
3.1 線程池
默認(rèn)情況下,Spring 使用SimpleAsyncTaskExecutor來執(zhí)行異步任務(wù),這個(gè)執(zhí)行器會(huì)為每個(gè)任務(wù)創(chuàng)建一個(gè)新的線程。在高并發(fā)場景下,這可能導(dǎo)致系統(tǒng)資源耗盡,因?yàn)閯?chuàng)建線程是一個(gè)比較耗費(fèi)資源的操作。而且過多的線程會(huì)增加上下文切換的成本,降低系統(tǒng)的整體性能。
例如,假設(shè)有一個(gè) Web 應(yīng)用,大量用戶同時(shí)觸發(fā)帶有@Async注解的方法,如果不配置線程池,可能會(huì)創(chuàng)建大量線程,使服務(wù)器的 CPU 和內(nèi)存資源被大量占用,最終導(dǎo)致應(yīng)用程序響應(yīng)緩慢甚至崩潰。
可以通過配置自定義的線程池來優(yōu)化。
例如,定義一個(gè)線程池配置類:
@Configuration
public class ThreadPoolConfig {
@Bean("asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
executor.setThreadNamePrefix("Async - ");
executor.initialize();
return executor;
}
}然后在@Async注解中指定線程池名稱:
@Async("asyncExecutor")
public void doSomethingAsync() {
// 方法內(nèi)容
}合理設(shè)置線程池參數(shù)
- 核心線程數(shù)(CorePoolSize):這是線程池一直保持的線程數(shù)量,即使線程處于空閑狀態(tài)也不會(huì)被銷毀。應(yīng)該根據(jù)應(yīng)用程序的平均負(fù)載來設(shè)置。例如,如果應(yīng)用程序通常需要同時(shí)處理 5 個(gè)異步任務(wù),那么可以將核心線程數(shù)設(shè)置為 5。
- 最大線程數(shù)(MaxPoolSize):它定義了線程池允許創(chuàng)建的最大線程數(shù)量。當(dāng)任務(wù)隊(duì)列已滿且有新任務(wù)到來時(shí),線程池會(huì)創(chuàng)建新線程,直到達(dá)到最大線程數(shù)。設(shè)置時(shí)要考慮系統(tǒng)資源限制和任務(wù)的突發(fā)情況。如果系統(tǒng)資源有限,不能無限制地增加線程數(shù)。
- 任務(wù)隊(duì)列容量(QueueCapacity):用于存儲(chǔ)等待執(zhí)行的任務(wù)。當(dāng)線程池中的線程都在忙碌時(shí),新任務(wù)會(huì)被放入任務(wù)隊(duì)列。如果隊(duì)列已滿,且未達(dá)到最大線程數(shù),才會(huì)創(chuàng)建新線程。隊(duì)列容量的大小應(yīng)該根據(jù)任務(wù)的平均處理時(shí)間和任務(wù)的產(chǎn)生頻率來確定。
線程池的復(fù)用和管理
- 配置好的線程池可以復(fù)用線程,提高線程的利用率。通過合理設(shè)置線程池的參數(shù),可以使線程在任務(wù)之間高效切換,減少線程創(chuàng)建和銷毀的開銷。同時(shí),需要注意線程池的生命周期管理,在應(yīng)用程序關(guān)閉時(shí),應(yīng)該正確地關(guān)閉線程池,以避免資源泄漏。
3.2 異常處理
異常不會(huì)自動(dòng)傳播給調(diào)用者,這是使用@Async時(shí)一個(gè)容易被忽視的問題。
當(dāng)異步方法拋出異常時(shí),異常不會(huì)像同步方法那樣直接傳播到調(diào)用者。這是因?yàn)楫惒椒椒ㄔ诹硪粋€(gè)線程中執(zhí)行,異常在這個(gè)線程中被拋出,如果不進(jìn)行特殊處理,調(diào)用者可能完全不知道異步方法出現(xiàn)了問題。
例如,在一個(gè)業(yè)務(wù)邏輯中,調(diào)用了一個(gè)帶有@Async注解的方法來更新數(shù)據(jù)庫記錄,若該異步方法在執(zhí)行過程中拋出了SQLException,如果沒有處理這個(gè)異常,調(diào)用者可能會(huì)繼續(xù)執(zhí)行后續(xù)的操作,認(rèn)為更新操作已經(jīng)成功,從而導(dǎo)致數(shù)據(jù)不一致等問題。
在異步方法內(nèi)部處理異常,可以在異步方法內(nèi)部使用try - catch塊來捕獲和處理異常。這樣可以在異步方法內(nèi)部對(duì)異常進(jìn)行記錄、重試或者進(jìn)行一些補(bǔ)救措施。
例如:
@Async
public void asyncMethod() {
try {
// 可能會(huì)拋出異常的代碼
} catch (Exception e) {
// 記錄異常日志
logger.error("異步方法出現(xiàn)異常", e);
// 可以在這里進(jìn)行重試或者其他補(bǔ)救措施
}
}配置全局異步異常處理機(jī)制,如果不想在每個(gè)異步方法內(nèi)部都處理異常,可以實(shí)現(xiàn)AsyncUncaughtExceptionHandler接口來配置全局的異步異常處理機(jī)制。這個(gè)接口有一個(gè)handleUncaughtException方法,當(dāng)異步方法拋出未捕獲的異常時(shí)會(huì)被調(diào)用。
例如:
@Configuration
@EnableAsync
public class AppConfig implements AsyncUncaughtExceptionHandler {
// 開啟異步支持的配置
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// 記錄異常日志
logger.error("異步方法出現(xiàn)未捕獲異常,方法名: " + method.getName(), ex);
// 可以在這里進(jìn)行全局的異常處理策略,如通知管理員等
}
}3.3 同類中調(diào)用異步方法
如果在一個(gè)類中,一個(gè)方法(方法 A)調(diào)用了同一個(gè)類中的另一個(gè)帶有@Async注解的方法(方法 B),默認(rèn)情況下@Async注解可能不會(huì)生效。
這是因?yàn)?Spring 的代理機(jī)制導(dǎo)致的,方法 A 直接調(diào)用方法 B 時(shí),實(shí)際上沒有通過代理對(duì)象來調(diào)用,所以不會(huì)觸發(fā)異步執(zhí)行。
例如,在一個(gè)Service類中:
@Service
public class MyService {
@Async
public void asyncMethod() {
// 異步執(zhí)行的代碼
}
public void anotherMethod() {
asyncMethod(); // 這種情況下,@Async可能不會(huì)生效
}
}解決方法是將方法 B 的調(diào)用通過注入的代理對(duì)象來進(jìn)行。可以通過@Autowired將當(dāng)前類自己注入進(jìn)來,然后通過代理對(duì)象調(diào)用方法 B。
例如:
@Service
public class MyService {
@Autowired
private MyService self;
@Async
public void asyncMethod() {
// 異步執(zhí)行的代碼
}
public void anotherMethod() {
self.asyncMethod(); // 通過代理對(duì)象調(diào)用,@Async生效
}
}3.4 循環(huán)依賴
在使用@Async時(shí),如果涉及到循環(huán)依賴,可能會(huì)導(dǎo)致應(yīng)用程序啟動(dòng)失敗或者出現(xiàn)異常行為。因?yàn)楫惒椒椒ǖ拇韺?duì)象創(chuàng)建和循環(huán)依賴的解決可能會(huì)相互沖突。
例如,有兩個(gè)服務(wù)類ServiceA和ServiceB,它們相互依賴并且都有@Async注解的方法。
在這種情況下,需要仔細(xì)檢查依賴注入的方式和異步方法的使用,避免出現(xiàn)循環(huán)依賴導(dǎo)致的問題。可以通過調(diào)整依賴注入的順序、使用@Lazy注解等方式來緩解循環(huán)依賴問題。
總結(jié)
到此這篇關(guān)于Java Spring的@Async的使用及注意事項(xiàng)的文章就介紹到這了,更多相關(guān)Spring的@Async使用及注意內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot使用jxls實(shí)現(xiàn)同sheet多個(gè)列表展示
這篇文章主要介紹了Springboot使用jxls實(shí)現(xiàn)同sheet多個(gè)列表展示,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
SpringCloud使用Nacos保存和讀取變量的配置方法
在使用SpringCloud開發(fā)微服務(wù)時(shí),經(jīng)常會(huì)遇到一些比較小的后臺(tái)參數(shù)配置,這些配置不足以單獨(dú)開一張表去存儲(chǔ),而且其他服務(wù)會(huì)讀取該參數(shù),這篇文章主要介紹了SpringCloud使用Nacos保存和讀取變量,需要的朋友可以參考下2022-07-07
java實(shí)現(xiàn)一個(gè)接口調(diào)取另一個(gè)接口(接口一調(diào)取接口二)
這篇文章主要介紹了java實(shí)現(xiàn)一個(gè)接口調(diào)取另一個(gè)接口(接口一調(diào)取接口二),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
在Java中動(dòng)態(tài)設(shè)置JVM參數(shù)的全過程
Java應(yīng)用程序在運(yùn)行期間動(dòng)態(tài)設(shè)置JVM參數(shù)的能力是有限的,主要取決于所要調(diào)整的具體參數(shù)類型,JVM參數(shù)大致可以分為兩類:系統(tǒng)屬性和啟動(dòng)參數(shù),下面將詳細(xì)介紹這兩類參數(shù)及其動(dòng)態(tài)設(shè)置的方法,需要的朋友可以參考下2025-10-10
Java?方法(方法的定義,可變參數(shù),參數(shù)的傳遞問題,方法重載,方法簽名)
這篇文章主要介紹了Java?方法(方法的定義,可變參數(shù),參數(shù)的傳遞問題,方法重載,方法簽名),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下2022-09-09
Springboot使用thymeleaf動(dòng)態(tài)模板實(shí)現(xiàn)刷新
這篇文章主要介紹了Springboot使用thymeleaf動(dòng)態(tài)模板實(shí)現(xiàn)刷新,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
java書店系統(tǒng)畢業(yè)設(shè)計(jì) 總體設(shè)計(jì)(1)
這篇文章主要介紹了java書店系統(tǒng)畢業(yè)設(shè)計(jì),第一步系統(tǒng)總體設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Java ArrayList擴(kuò)容問題實(shí)例詳解
這篇文章主要介紹了Java ArrayList擴(kuò)容問題實(shí)例詳解,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02

