簡(jiǎn)介
在當(dāng)今高速發(fā)展的應(yīng)用開發(fā)領(lǐng)域,對(duì)于提升系統(tǒng)性能和響應(yīng)能力的需求越來越迫切。而異步編程作為一種解決方案,已經(jīng)成為現(xiàn)代應(yīng)用開發(fā)中的一項(xiàng)重要技術(shù)。本篇博客將帶您深入探究 Java 中的 @Async 注解,揭示其強(qiáng)大的異步執(zhí)行能力和精妙的實(shí)現(xiàn)機(jī)制。
異步編程是一種編程模式,通過將任務(wù)分解為多個(gè)子任務(wù),并在后臺(tái)或并行線程中執(zhí)行這些子任務(wù),以提高程序的性能和響應(yīng)能力。
@Async 注解簡(jiǎn)介
@Async 注解是 Spring 框架提供的注解,用于將方法標(biāo)記為異步執(zhí)行的方法。它的作用是告訴 Spring 框架在調(diào)用被注解的方法時(shí),將其放入線程池中異步執(zhí)行,而不是阻塞等待方法的完成。
@Async 注解的工作原理是,在調(diào)用被注解的方法時(shí),Spring 會(huì)將該方法的執(zhí)行轉(zhuǎn)移到線程池中的一個(gè)線程進(jìn)行處理。執(zhí)行完成后,方法的返回值將通過 Future 或 CompletableFuture 進(jìn)行封裝,以便獲取方法的返回結(jié)果。
-
@Async 注解適用于以下場(chǎng)景,并具有以下優(yōu)勢(shì):
- 網(wǎng)絡(luò)請(qǐng)求:在處理網(wǎng)絡(luò)請(qǐng)求時(shí),可以使用 @Async 注解將請(qǐng)求發(fā)送和響應(yīng)處理分離,提高系統(tǒng)的并發(fā)處理能力。
- 耗時(shí)計(jì)算:對(duì)于需要耗費(fèi)大量時(shí)間的計(jì)算任務(wù),可以使用 @Async 注解將計(jì)算過程放在后臺(tái)執(zhí)行,避免阻塞主線程,提高系統(tǒng)的響應(yīng)速度。
- 并行處理:通過 @Async 注解,可以同時(shí)執(zhí)行多個(gè)任務(wù),將多個(gè)相互獨(dú)立的任務(wù)并行處理,從而減少整體處理時(shí)間。
- 響應(yīng)能力提升:使用異步編程可以避免阻塞主線程,提高系統(tǒng)的并發(fā)能力和響應(yīng)能力,增強(qiáng)用戶體驗(yàn)。
- 代碼簡(jiǎn)化:使用 @Async 注解可以簡(jiǎn)化編程模型,將異步執(zhí)行的邏輯與業(yè)務(wù)邏輯分離,使代碼更清晰、易于維護(hù)。
異步執(zhí)行通過將任務(wù)分解為多個(gè)并發(fā)執(zhí)行的子任務(wù),可以充分利用系統(tǒng)資源,提高系統(tǒng)的吞吐量和并發(fā)處理能力,從而提升系統(tǒng)的性能和響應(yīng)能力。@Async 注解簡(jiǎn)化了異步編程的實(shí)現(xiàn),使開發(fā)人員能夠更方便地使用異步處理機(jī)制。同時(shí),它還可以使代碼更易于閱讀和維護(hù),提高開發(fā)效率。
@Async 注解的源碼解析
@Async 注解在 Spring 框架中的實(shí)現(xiàn)主要依賴于以下幾個(gè)關(guān)鍵組件:
-
AsyncAnnotationBeanPostProcessor
:這是一個(gè) Bean 后置處理器,負(fù)責(zé)解析帶有 @Async 注解的方法,將其包裝成異步任務(wù)。 -
AsyncTaskExecutor
:這是一個(gè)任務(wù)執(zhí)行器,用于執(zhí)行異步任務(wù)??梢酝ㄟ^配置來指定具體的線程池或任務(wù)調(diào)度器。 -
AsyncConfigurer
:這是一個(gè)可選的接口,用于提供自定義的異步任務(wù)執(zhí)行器。
在 Spring 框架中,當(dāng)啟用異步支持時(shí),AsyncAnnotationBeanPostProcessor
會(huì)掃描容器中的 Bean,并檢查其中的方法是否標(biāo)記有 @Async 注解。如果發(fā)現(xiàn)帶有 @Async 注解的方法,它將會(huì)將其封裝成一個(gè)代理對(duì)象,并注冊(cè)為一個(gè)可執(zhí)行的異步任務(wù)。
當(dāng)調(diào)用被 @Async 注解標(biāo)記的方法時(shí),實(shí)際上是調(diào)用了該方法的代理對(duì)象。代理對(duì)象會(huì)將方法的執(zhí)行轉(zhuǎn)移到線程池中的一個(gè)線程進(jìn)行處理,并返回一個(gè) Future 對(duì)象,用于獲取方法的返回結(jié)果。
線程池的配置可以通過 Spring 的配置文件或編程方式進(jìn)行指定。可以配置線程池的大小、線程池的類型(如固定大小線程池、緩存線程池等)以及任務(wù)調(diào)度策略等。
異步方法與事務(wù)的關(guān)系
在使用 @Async 注解標(biāo)記的異步方法與事務(wù)之間存在一些關(guān)系和注意事項(xiàng)。
- 默認(rèn)情況下,異步方法不受事務(wù)管理的影響。當(dāng)一個(gè)帶有 @Transactional 注解的方法調(diào)用一個(gè)標(biāo)記為 @Async 的異步方法時(shí),異步方法將在一個(gè)新的線程中執(zhí)行,與原始方法的事務(wù)無關(guān)。
- 異步方法獨(dú)立事務(wù)。如果希望異步方法能夠參與到事務(wù)管理中,可以使用 Propagation.REQUIRES_NEW 傳播行為。將異步方法設(shè)置為 @Transactional(propagation = Propagation.REQUIRES_NEW) ,這樣異步方法將在新的事務(wù)中執(zhí)行,與原始方法的事務(wù)隔離開來。
- 異步方法和事務(wù)的提交。由于異步方法是在獨(dú)立的線程中執(zhí)行的,與原始方法的事務(wù)是分離的。因此,異步方法中的事務(wù)提交操作不會(huì)對(duì)原始方法的事務(wù)產(chǎn)生影響。即使異步方法中的事務(wù)提交失敗,也不會(huì)導(dǎo)致原始方法的事務(wù)回滾。
- 異步方法和事務(wù)的異常處理。異步方法中的異常默認(rèn)是不會(huì)被捕獲和處理的,除非在異步方法中顯式地進(jìn)行了異常處理。如果需要對(duì)異步方法中的異常進(jìn)行處理,可以使用 AsyncUncaughtExceptionHandler 接口來自定義異常處理邏輯。
需要注意的是,使用異步方法與事務(wù)的組合可能會(huì)帶來一些潛在的問題和風(fēng)險(xiǎn),如數(shù)據(jù)不一致性、并發(fā)沖突等。在使用異步方法和事務(wù)的同時(shí),需要仔細(xì)考慮業(yè)務(wù)需求和數(shù)據(jù)一致性的要求,確保邏輯正確性和數(shù)據(jù)完整性。
總結(jié)起來,異步方法和事務(wù)之間的關(guān)系可以通過設(shè)置事務(wù)的傳播行為來調(diào)整。默認(rèn)情況下,異步方法是獨(dú)立于事務(wù)的,可以通過設(shè)置 Propagation.REQUIRES_NEW 傳播行為使異步方法參與到事務(wù)管理中。然而,需要注意并發(fā)和數(shù)據(jù)一致性的問題,并根據(jù)具體業(yè)務(wù)需求合理使用異步方法和事務(wù)的組合。
Async異常處理
在使用 @Async進(jìn)行異步方法調(diào)用時(shí),異常處理是一個(gè)重要的方面。以下是異步方法的異常處理機(jī)制:
- 默認(rèn)情況下,異步方法的異常會(huì)被捕獲并封裝為Future對(duì)象(或CompletableFuture對(duì)象)。您可以通過Future.get() 方法或CompletableFuture.get() 方法獲取異步任務(wù)的結(jié)果,并在調(diào)用時(shí)捕獲異常。如果異步任務(wù)拋出異常,將會(huì)在調(diào)用get() 方法時(shí)重新拋出異常,您可以在調(diào)用端進(jìn)行異常處理。
@Async
public CompletableFuture<String> performTask() {
// 異步任務(wù)邏輯
}
// 調(diào)用異步方法并處理異常
CompletableFuture<String> future = myService.performTask();
try {
String result = future.get();
// 處理正常結(jié)果
} catch (InterruptedException | ExecutionException e) {
// 處理異常情況
}
- 您還可以使用AsyncUncaughtExceptionHandler接口來處理異步方法中未捕獲的異常。通過實(shí)現(xiàn)AsyncUncaughtExceptionHandler接口,并在AsyncConfigurer中重寫getAsyncUncaughtExceptionHandler() 方法,您可以定義全局的異步異常處理邏輯。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
// 配置異步方法執(zhí)行器
@Override
public Executor getAsyncExecutor() {
// 配置任務(wù)執(zhí)行器
}
// 配置異步方法未捕獲異常處理器
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
// 其他配置...
}
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
// 處理異步方法未捕獲的異常
Class<?> clazz = method.getDeclaringClass();
String message = String.format("異步方法執(zhí)行失敗,具體類名: %s, 方法名:%s, 異常信息: %s", clazz.getName(), method.getName(), ex);
log.error("異步方法執(zhí)行失敗,具體類名: {}, 方法名:{}, 方法入?yún)?{}, 異常信息: {}", clazz.getName(), method.getName(), Arrays.toString(params), ex.getMessage(), ex);
}
}
在上述示例中,CustomAsyncExceptionHandler實(shí)現(xiàn)了AsyncUncaughtExceptionHandler接口,并實(shí)現(xiàn)了handleUncaughtException() 方法來處理異步方法中未捕獲的異常。您可以在該方法中編寫自定義的異常處理邏輯,例如日志記錄、錯(cuò)誤報(bào)警等。
通過上述異常處理機(jī)制,您可以捕獲和處理異步方法中的異常,從而確保對(duì)異步任務(wù)的異常情況進(jìn)行適當(dāng)?shù)奶幚怼?/p>
ThreadLocal和Async使用問題
在工作過程中,經(jīng)常遇到這個(gè)問題,系統(tǒng)通常會(huì)通過攔截器獲取用戶信息并設(shè)置到ThreadLoacl中,但是在異步方法中獲取用戶信息,卻出現(xiàn)了獲取到了其他用戶信息的問題。
這是因?yàn)锧Async注解會(huì)在異步執(zhí)行方法時(shí)切換線程,而線程切換會(huì)導(dǎo)致ThreadLocal中的內(nèi)容無法被正確傳遞。
解決這個(gè)問題的一種方法是使用AsyncTaskExecutor的子類,例如ThreadPoolTaskExecutor,并在配置中設(shè)置TaskDecorator。TaskDecorator可以在每次異步任務(wù)執(zhí)行時(shí)對(duì)線程進(jìn)行修飾,以確保ThreadLocal中的內(nèi)容被正確傳遞。
以下是一個(gè)示例配置的代碼片段:
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new ThreadLocalTaskDecorator()); // 設(shè)置TaskDecorator
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}
// 自定義的TaskDecorator
private static class ThreadLocalTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 保存當(dāng)前線程的ThreadLocal內(nèi)容
// 獲取調(diào)用線程的 traceId 和用戶信息
String traceId = ThreadLocalUtils.getTraceId();
User user = ThreadLocalUtils.getUser();
return () -> {
try {
// 恢復(fù)之前保存的ThreadLocal內(nèi)容
// 在子線程中設(shè)置 traceId 和用戶信息
ThreadLocalUtils.setTraceId(traceId);
ThreadLocalUtils.setUser(user);
runnable.run();
} finally {
// 清除子線程的 traceId 和用戶信息
ThreadLocalUtils.clear();
}
};
}
}
}
在上述示例中,我們使用ThreadLocalContextHolder類來管理ThreadLocal的操作,包括設(shè)置、獲取和清理ThreadLocal中的內(nèi)容。
public class ThreadLocalUtils {
private static final ThreadLocal<String> traceIdThreadLocal = new ThreadLocal<>();
private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static String getTraceId() {
return traceIdThreadLocal.get();
}
public static void setTraceId(String traceId) {
traceIdThreadLocal.set(traceId);
}
public static User getUser() {
return userThreadLocal.get();
}
public static void setUser(User user) {
userThreadLocal.set(user);
}
public static void clear() {
traceIdThreadLocal.remove();
userThreadLocal.remove();
}
}
通過使用以上的配置和ThreadLocalTaskDecorator,你可以確保在異步執(zhí)行時(shí),ThreadLocal中的用戶信息能夠正確傳遞并被獲取到。
多線程池配置
如果您需要配置多個(gè)不同類型的 @Async注解,并且使用不同的線程池類型(緩存線程池和固定線程池),可以按照以下方式進(jìn)行配置:
首先,創(chuàng)建多個(gè)線程池和相應(yīng)的TaskExecutor bean。
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
// 緩存線程池
@Bean("cachedThreadPool")
public TaskExecutor cachedThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(0); // 根據(jù)實(shí)際情況調(diào)整核心線程數(shù)
executor.setMaxPoolSize(Integer.MAX_VALUE); // 根據(jù)實(shí)際情況調(diào)整最大線程數(shù)
executor.setQueueCapacity(100); // 根據(jù)實(shí)際情況調(diào)整隊(duì)列容量
executor.setThreadNamePrefix("cached-thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setTaskDecorator(new MdcTaskDecorator()); // 設(shè)置任務(wù)裝飾器
executor.initialize();
return executor;
}
// 固定線程池
@Bean("fixedThreadPool")
public TaskExecutor fixedThreadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 根據(jù)實(shí)際情況調(diào)整核心線程數(shù)
executor.setMaxPoolSize(10); // 根據(jù)實(shí)際情況調(diào)整最大線程數(shù)
executor.setQueueCapacity(0); // 不使用隊(duì)列,直接執(zhí)行
executor.setThreadNamePrefix("fixed-thread-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setTaskDecorator(new MdcTaskDecorator()); // 設(shè)置任務(wù)裝飾器
executor.initialize();
return executor;
}
// 配置異步方法執(zhí)行器
@Override
public Executor getAsyncExecutor() {
return cachedThreadPool();
}
// 配置自定義的異步方法執(zhí)行器,用于特定類型的異步任務(wù)
@Bean("customAsyncExecutor")
public Executor customAsyncExecutor() {
return fixedThreadPool();
}
// 其他配置...
}
在上述示例中,我們創(chuàng)建了兩個(gè)不同類型的線程池:cachedThreadPool和fixedThreadPool,并將它們作為TaskExecutor bean 注冊(cè)到Spring容器中。
通過以上配置,您可以使用不同的線程池類型為不同類型的異步任務(wù)配置不同的執(zhí)行器,并根據(jù)需求調(diào)整線程池的屬性。
最佳實(shí)踐和注意事項(xiàng)
在使用異步方法時(shí),需要注意以下幾點(diǎn):文章來源:http://www.zghlxwxcb.cn/news/detail-753006.html
- 異步方法應(yīng)盡量保持簡(jiǎn)單和獨(dú)立,不涉及復(fù)雜的事務(wù)邏輯。
- 異步方法的執(zhí)行時(shí)間應(yīng)控制在合理的范圍內(nèi),避免因長(zhǎng)時(shí)間執(zhí)行導(dǎo)致線程資源占用過多。
- 需要考慮異步方法與其他業(yè)務(wù)邏輯的協(xié)調(diào),確保異步方法的執(zhí)行順序和結(jié)果正確性。
- 異步方法的并發(fā)性可能導(dǎo)致資源競(jìng)爭(zhēng)和并發(fā)訪問的問題,需要進(jìn)行適當(dāng)?shù)牟l(fā)控制和線程安全處理。
github: https://github.com/kong0827
博客: https://juejin.cn/user/3403743731154190/posts文章來源地址http://www.zghlxwxcb.cn/news/detail-753006.html
到了這里,關(guān)于深入理解Spring的@Async注解:實(shí)現(xiàn)異步方法調(diào)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!