SpringBoot異步使用@Async的原理以及線程池配置詳解
前言
在實(shí)際項(xiàng)目開發(fā)中很多業(yè)務(wù)場景需要使用異步去完成,比如消息通知,日志記錄,等非常常用的都可以通過異步去執(zhí)行,提高效率,那么在Spring框架中應(yīng)該如何去使用異步呢
使用步驟
完成異步操作一般有兩種,消息隊(duì)列MQ,和線程池處理ThreadPoolExecutor
而在Spring4中提供的對ThreadPoolExecutor封裝的線程池ThreadPoolTaskExecutor,直接使用注解啟用@Async,這個(gè)注解讓我們在使用Spring完成異步操作變得非常方便
配置線程池類參數(shù)配置
自定義常量類
public class ConstantFiledUtil { public static final String AUTHORIZATION_TOKEN = "authorizationToken"; /** * kmall線程池名稱 */ public static final String KMALL_THREAD_POOL = "KmallThreadPool"; /** * kmall線程名稱前綴 */ public static final String KMALL_THREAD_NAME_PREFIX = "kmall-thread-"; }
配置線程池
@Configuration(proxyBeanMethods = false) @EnableAsync //開啟注解 public class KmallConfig { @Bean(ConstantFiledUtil.KMALL_THREAD_POOL) public ThreadPoolTaskExecutor FebsShiroThreadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置核心線程數(shù) executor.setCorePoolSize(5); //配置最大線程數(shù) executor.setMaxPoolSize(20); //配置隊(duì)列大小 executor.setQueueCapacity(200); //線程池維護(hù)線程所允許的空閑時(shí)間 executor.setKeepAliveSeconds(30); //配置線程池中的線程的名稱前綴 executor.setThreadNamePrefix(ConstantFiledUtil.KMALL_THREAD_NAME_PREFIX); //設(shè)置線程池關(guān)閉的時(shí)候等待所有任務(wù)都完成再繼續(xù)銷毀其他的Bean executor.setWaitForTasksToCompleteOnShutdown(true); //設(shè)置線程池中任務(wù)的等待時(shí)間,如果超過這個(gè)時(shí)候還沒有銷毀就強(qiáng)制銷毀,以確保應(yīng)用最后能夠被關(guān)閉,而不是阻塞住 executor.setAwaitTerminationSeconds(60); // rejection-policy:當(dāng)pool已經(jīng)達(dá)到max size的時(shí)候,如何處理新任務(wù) // CALLER_RUNS:不在新線程中執(zhí)行任務(wù),而是由調(diào)用者所在的線程來執(zhí)行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //執(zhí)行初始化 executor.initialize(); return executor; } }
注意這里需要通過@EnableAsync開啟異步否則無效
自定義線程任務(wù)
public interface ILogService extends IService<Log> { /** * 分頁搜索查詢?nèi)罩拘畔? * @param logParams * @return */ IPage getSearchLogByPage(SearchLogParams logParams); /** * 保存日志 * * @param logSubject */ @Async void saveLog(LogSubject logSubject); }
在需要異步執(zhí)行的接口,或者方法上加上@Async注解此方法就是異步方法,在主程序中調(diào)用的化,就是異步方式單獨(dú)線程執(zhí)行
此注解表明saveLog方法進(jìn)入的線程池是KmallThreadPool方法創(chuàng)建的。
我們也可以單獨(dú)指定方法名稱@Async("saveLogs")
這樣在進(jìn)行日記記錄的時(shí)候就是單獨(dú)線程執(zhí)行每次請求都快速響應(yīng)了,而耗時(shí)的操作都留給線程池中的線程去異步執(zhí)行
總結(jié)
Spring中用@Async注解標(biāo)記的方法,稱為異步方法。在spring boot應(yīng)用中使用@Async很簡單:
- 調(diào)用異步方法類上或者啟動(dòng)類加上注解@EnableAsync
- 在需要被異步調(diào)用的方法外加上@Async
- 所使用的@Async注解方法的類對象應(yīng)該是Spring容器管理的bean對象;
注意同一個(gè)類里面調(diào)用異步方法不生效:原因默認(rèn)類內(nèi)的方法調(diào)用不會(huì)被aop攔截,即調(diào)用方和被調(diào)用方是在同一個(gè)類中,是無法產(chǎn)生切面的,該對象沒有被Spring容器管理。即@Async方法不生效
解決辦法:
如果要使同一個(gè)類中的方法之間調(diào)用也被攔截,需要使用spring容器中的實(shí)例對象,而不是使用默認(rèn)的this,因?yàn)橥ㄟ^bean實(shí)例的調(diào)用才會(huì)被spring的aop攔截
本例使用方法:AsyncService asyncService = context.getBean(AsyncService.class); 然后使用這個(gè)引用調(diào)用本地的方法即可達(dá)到被攔截的目的
備注:這種方法只能攔截protected,default,public方法,private方法無法攔截。這個(gè)是spring aop的一個(gè)機(jī)制。
原理刨析
異步方法返回類型只能有兩種:
- 當(dāng)返回類型為void的時(shí)候,方法調(diào)用過程產(chǎn)生的異常不會(huì)拋到調(diào)用者層面,可以通過注AsyncUncaughtExceptionHandler來捕獲此類異常
- 當(dāng)返回類型為Future的時(shí)候,方法調(diào)用過程產(chǎn)生的異常會(huì)拋到調(diào)用者層面
注意如果不自定義異步方法的線程池默認(rèn)使用SimpleAsyncTaskExecutor。SimpleAsyncTaskExecutor:不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。并發(fā)大的時(shí)候會(huì)產(chǎn)生嚴(yán)重的性能問題。
Spring異步線程池接口 TaskExecutor
看源碼可知
@FunctionalInterface public interface TaskExecutor extends Executor { void execute(Runnable var1); }
它的實(shí)先類有很多如下:
- SimpleAsyncTaskExecutor:不是真的線程池,這個(gè)類不重用線程,每次調(diào)用都會(huì)創(chuàng)建一個(gè)新的線程。
- SyncTaskExecutor:這個(gè)類沒有實(shí)現(xiàn)異步調(diào)用,只是一個(gè)同步操作。只適用于不需要多線程的地方
- ConcurrentTaskExecutor:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時(shí),才用考慮使用這個(gè)類
- SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的類。線程池同時(shí)被quartz和非quartz使用,才需要使用此類
- ThreadPoolTaskExecutor :最常使用,推薦。 其實(shí)質(zhì)是對java.util.concurrent.ThreadPoolExecutor的包裝,
文章參考
- 簡述Springboot @Async 異步方法
- springboot實(shí)現(xiàn)異步調(diào)用@Async的示例
- SpringBoot用@Async注解實(shí)現(xiàn)異步任務(wù)
- 詳解springboot使用異步注解@Async獲取執(zhí)行結(jié)果的坑
- springboot?@Async?注解如何實(shí)現(xiàn)方法異步
- SpringBoot使用@Async注解處理異步事件的方法
- Springboot如何使用@Async實(shí)現(xiàn)異步任務(wù)
- SpringBoot使用@Async注解實(shí)現(xiàn)異步調(diào)用
- springboot異步@Async的使用及失效場景介紹
相關(guān)文章
深入解析Java編程中的StringBuffer與StringBuider
這篇文章主要介紹了Java編程中的StringBuffer與StringBuider,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09Spring boot實(shí)現(xiàn)上傳文件到本地服務(wù)器
這篇文章主要為大家詳細(xì)介紹了Spring boot實(shí)現(xiàn)上傳文件到本地服務(wù)器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08springboot中@Async默認(rèn)線程池導(dǎo)致OOM問題
這篇文章主要介紹了springboot中@Async默認(rèn)線程池導(dǎo)致OOM問題,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼
在我們?nèi)粘i_發(fā)過程中,通常會(huì)涉及到數(shù)據(jù)權(quán)限問題,本文主要介紹了Mybatis攔截器實(shí)現(xiàn)數(shù)據(jù)權(quán)限的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03java Person,Student,GoodStudent 三個(gè)類的繼承、構(gòu)造函數(shù)的執(zhí)行
這篇文章主要介紹了java Person,Student,GoodStudent 三個(gè)類的繼承、構(gòu)造函數(shù)的執(zhí)行,需要的朋友可以參考下2017-02-02java String類常量池分析及"equals"和"==”區(qū)別詳細(xì)介紹
這篇文章主要介紹了java String類常量池分析及"equals"和"==”區(qū)別詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-12-12