Java Spring注解之@Async的基本用法和示例
背景
通常,在Java中的方法調(diào)用都是同步調(diào)用,比如在A方法中調(diào)用了B方法,則在A調(diào)用B方法之后,必須等待B方法執(zhí)行并返回后,A方法才可以繼續(xù)往下執(zhí)行。這樣容易出現(xiàn)的一個問題就是如果B方法執(zhí)行時間較長,則可能會導(dǎo)致調(diào)用A的請求響應(yīng)遲緩,為了解決這種問題,可以使用Spirng的注解@Async來用異步調(diào)用的方式處理,當然也會有別的多線程方式解決此類問題,本文主要分析@Async在解決此類問題時的用法以及具體的示例。
異步調(diào)用
比如方法A調(diào)用方法B,如果B是一個異步方法,則A方法在調(diào)用B方法之后,不用等待B方法執(zhí)行完成,而是直接往下繼續(xù)執(zhí)行別的代碼。
@Async介紹
在Spring中,使用@Async標注某方法,可以使該方法變成異步方法,這些方法在被調(diào)用的時候,將會在獨立的線程中進行執(zhí)行,調(diào)用者不需等待該方法執(zhí)行完成。
在Spring中啟用@Async
使用@EnableAsync
@Slf4j @SpringBootApplication @ComponentScan(basePackages = {"com.kaesar.spring"}) @EnableAsync // 開啟異步調(diào)用 public class Application { public static void main(String[] args) { log.info("spring boot開始啟動..."); ApplicationContext ctx = SpringApplication.run(Application.class, args); String[] activeProfiles = ctx.getEnvironment().getActiveProfiles(); for (String profile : activeProfiles) { log.info("當前環(huán)境為:" + profile); } log.info("spring boot啟動成功..."); } }
示例一:基本使用方式
在方法上添加@Async注解
/** * 異步方法 * 默認情況下,Spring 使用 SimpleAsyncTaskExecutor 去執(zhí)行這些異步方法(此執(zhí)行器沒有限制線程數(shù))。 * 此默認值可以從兩個層級進行覆蓋: * 方法級別 * 應(yīng)用級別 */ @Async public void test2() { try { log.info(Thread.currentThread().getName() + " in test2, before sleep."); Thread.sleep(2000); log.info(Thread.currentThread().getName() + " in test2, after sleep."); } catch (InterruptedException e) { log.error("sleep error."); } }
調(diào)用異步方法
/** * 調(diào)用不同類的異步方法 */ public void func1() { log.info("before call async function."); asyncService.test2(); log.info("after call async function."); try { Thread.sleep(3000); } catch (InterruptedException e) { log.error("sleep error."); } log.info("func end."); }
從執(zhí)行結(jié)果可以看出,main線程中的func1方法在調(diào)用異步方法test2后,沒有等待test2方法執(zhí)行完成,直接執(zhí)行后面的代碼。
示例二:在同一個類中調(diào)用異步方法
方法func2和上面的異步方法test2方法在同一個類中
從執(zhí)行結(jié)果可知,main線程中的func2方法在調(diào)用異步方法test2方法后,等待test2方法執(zhí)行完后,才繼續(xù)往后執(zhí)行。
示例三:異步方法是static方法
異步方法test3是一個static方法
/** * 異步方法不能是 static 方法,不然注解失效 */ @Async public static void test3() { try { log.info(Thread.currentThread().getName() + " in test3, before sleep."); Thread.sleep(2000); log.info(Thread.currentThread().getName() + " in test3, after sleep."); } catch (InterruptedException e) { log.error("sleep error."); } }
調(diào)用test3的方法
/** * 調(diào)用不同類的異步方法,異步方法是 static 方法 */ public void func3() { log.info(Thread.currentThread().getName() + ": before call async function."); AsyncService.test3(); log.info(Thread.currentThread().getName() + ": after call async function."); try { Thread.sleep(3000); } catch (InterruptedException e) { log.error("sleep error."); } log.info(Thread.currentThread().getName() + ": func end."); }
執(zhí)行結(jié)果??梢钥闯鲈趕tatic方法上添加@Async注解,當調(diào)用該方法時并沒有新啟用一個線程單獨執(zhí)行,而是按順序執(zhí)行代碼,說明異步無效。
示例四:在方法級別上修改默認的執(zhí)行器
自定義一個線程池執(zhí)行器代替默認的執(zhí)行器
自定義的線程池執(zhí)行器
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; /** * 自定義線程池 */ @Configuration public class AsyncConfig { private static final int MAX_POOL_SIZE = 10; private static final int CORE_POOL_SIZE = 5; @Bean("asyncTaskExecutor") public AsyncTaskExecutor asyncTaskExecutor() { ThreadPoolTaskExecutor asyncTaskExecutor = new ThreadPoolTaskExecutor(); asyncTaskExecutor.setMaxPoolSize(MAX_POOL_SIZE); asyncTaskExecutor.setCorePoolSize(CORE_POOL_SIZE); asyncTaskExecutor.setThreadNamePrefix("async-task-thread-pool-"); asyncTaskExecutor.initialize(); return asyncTaskExecutor; } }
異步方法上使用自定義的執(zhí)行器
/** * 在方法級別上修改默認的執(zhí)行器 */ @Async("asyncTaskExecutor") public void test4() { try { log.info(Thread.currentThread().getName() + ": in test4, before sleep."); Thread.sleep(2000); log.info(Thread.currentThread().getName() + ": in test4, after sleep."); } catch (InterruptedException e) { log.error("sleep error."); } }
調(diào)用test4異步方法
/** * 調(diào)用不同類的異步方法 */ public void func4() { log.info(Thread.currentThread().getName() + ": before call async function."); asyncService.test4(); log.info(Thread.currentThread().getName() + ": after call async function."); try { Thread.sleep(3000); } catch (InterruptedException e) { log.error("sleep error."); } log.info(Thread.currentThread().getName() + ": func end."); }
從執(zhí)行結(jié)果可以看出,@Async注解聲明使用指定的自定義的異步執(zhí)行器,已經(jīng)替換了默認的執(zhí)行器。而且調(diào)用異步方法的main線程沒有等待異步方法的執(zhí)行。
說明:新建自定義的執(zhí)行器后,注解@Async默認就會替換成自定義的執(zhí)行器,所以在@Async注解上可以不用指定。
\(1.01^{365} ≈ 37.7834343329\)
\(0.99^{365} ≈ 0.02551796445\)
相信堅持的力量!
補充:Java中異步注解@Async的陷阱
或許,你在Java后端添加異步過程時會這樣處理,然后搖搖大擺、灰溜溜地閃,而實際的運行結(jié)果卻并不是我們期望的那樣。那么,現(xiàn)在就將試驗結(jié)果記錄如下,以便少走彎路。
(一)在Controller層的公開接口直接添加@Async注解
當前端調(diào)用該種接口時會立刻結(jié)束,意味著開始即結(jié)束,不會在乎該異步接口返回的數(shù)據(jù),其實這種接口只適合前端下發(fā)命令,后續(xù)就不管后端的處理流程了,也不需要后端返回的對象。
(二)在Controller層的私有接口直接添加@Async注解
這種情況是,前端調(diào)用后端的公開接口并等待該接口返回,此時在該接口中調(diào)用了該層的添加了@Async注解的私有方法,也許你期待的是讓后端接口立刻返回,讓具體的處理過程放在@Async注解的私有函數(shù)中,可事實并沒有達到你的效果,添加了@Async注解的私有函數(shù)依舊是同步過程,即使你在Controller層的類前面添加了@EnableAsync注解,也無濟于事;所以,這種方式達不到異步的效果。我們可以通過日志來驗證該過程,如下圖所示:
在上圖中,我們看到先進入Controller層公開接口,然后進入帶有@Async注解的私有方法,接著跳出,最后又回到Controller層公開接口,整個流程就是同步過程,此時的@Async注解沒有效果。
(三)在Service層的公開接口直接添加@Async注解
在Controller層提供同步流程的接口,只是在該層中會調(diào)用Service層的異步接口,只需要在需要用異步流程完成任務(wù)的接口上方添加@Async注解即可,這種策略是可以實現(xiàn)我們的異步過程的,我們還是通過日志來驗證該流程,如下圖所示:
在上圖中,我們看到流程首先進入Controller層,然后立即跳出了Controller層,而Service層的異步接口就是后續(xù)完成的任務(wù)了,這樣的流程已達到我們想要的異步過程了。
總結(jié)
到此這篇關(guān)于Java中@Async的基本用法和示例的文章就介紹到這了,更多相關(guān)java @Async的用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot使用Redis單機版過期鍵監(jiān)聽事件的實現(xiàn)示例
在緩存的使用場景中經(jīng)常需要使用到過期事件,本文主要介紹了SpringBoot使用Redis單機版過期鍵監(jiān)聽事件的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07spring.profiles.active配置使用小結(jié)
spring.profiles.active?配置使得應(yīng)用程序能夠在不同的環(huán)境中使用不同的配置,本文主要介紹了spring.profiles.active配置使用小結(jié),具有一定的參考價值,感興趣的可以了解一下2024-07-07mybatis插件優(yōu)雅實現(xiàn)字段加密的示例代碼
在很多時候,我們都需要字段加密,比如郵箱,密碼,電話號碼等,本文主要介紹了mybatis插件優(yōu)雅實現(xiàn)字段加密的示例代碼,感興趣的可以了解一下2023-11-11SpringBoot @value注解動態(tài)刷新問題小結(jié)
@Value注解 所對應(yīng)的數(shù)據(jù)源來自項目的 Environment 中,我們可以將數(shù)據(jù)庫或其他文件中的數(shù)據(jù),加載到項目的 Environment 中,然后 @Value注解 就可以動態(tài)獲取到配置信息了,這篇文章主要介紹了SpringBoot @value注解動態(tài)刷新,需要的朋友可以參考下2023-09-09