一文搞懂SpringBoot如何利用@Async實(shí)現(xiàn)異步調(diào)用
前言
異步調(diào)用幾乎是處理高并發(fā),解決性能問題常用的手段,如何開啟異步調(diào)用?SpringBoot中提供了非常簡單的方式,就是一個(gè)注解@Async。今天我們重新認(rèn)識(shí)一下@Async,以及注意事項(xiàng)
簡單使用
新建三個(gè)作業(yè)任務(wù):
@Service public class TaskDemo { private static Logger logger = LoggerFactory.getLogger(TaskDemo.class); public void execute1() { logger.info("處理耗時(shí)任務(wù)1......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)1......結(jié)束"); } public void execute2() { logger.info("處理耗時(shí)任務(wù)2......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)2......結(jié)束"); } public void execute3() { logger.info("處理耗時(shí)任務(wù)3......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)3......結(jié)束"); } }
測試代碼:
@RestController public class TaskController { @Autowired private TaskDemo taskDemo; @GetMapping("/task/test") public String testTask() { taskDemo.execute1(); taskDemo.execute2(); taskDemo.execute3(); return "ok"; } }
執(zhí)行后我們可以發(fā)現(xiàn),上面的代碼是同一個(gè)線程的同步執(zhí)行,整體耗時(shí)9秒才完成。
異步處理
springboot的異步,是非常簡單的,加2個(gè)注解即可
@Service public class TaskDemo { private static Logger logger = LoggerFactory.getLogger(TaskDemo.class); @Async public void execute1() { logger.info("處理耗時(shí)任務(wù)1......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)1......結(jié)束"); } @Async public void execute2() { logger.info("處理耗時(shí)任務(wù)2......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)2......結(jié)束"); } @Async public void execute3() { logger.info("處理耗時(shí)任務(wù)3......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)3......結(jié)束"); } }
@SpringBootApplication @EnableAsync public class DemoApp { public static void main(String[] args) { SpringApplication.run(DemoApp.class,args); } }
增加了@Async和@EnableAsync兩個(gè)注解
從執(zhí)行結(jié)果發(fā)現(xiàn),整個(gè)流程用了3秒,以及用了3個(gè)線程執(zhí)行。完成了異步調(diào)用
異步回調(diào)
有些場景我們需要知道異步處理的任務(wù)什么時(shí)候完成,需要做額外的業(yè)務(wù)處理。如:我們需要在3個(gè)任務(wù)都完成后,提示一下給用戶
@Service public class TaskDemo { private static Logger logger = LoggerFactory.getLogger(TaskDemo.class); @Async public Future<String> execute1() { logger.info("處理耗時(shí)任務(wù)1......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)1......結(jié)束"); return new AsyncResult<>("任務(wù)1 ok"); } @Async public Future<String> execute2() { logger.info("處理耗時(shí)任務(wù)2......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)2......結(jié)束"); return new AsyncResult<>("任務(wù)2 ok"); } @Async public Future<String> execute3() { logger.info("處理耗時(shí)任務(wù)3......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)3......結(jié)束"); return new AsyncResult<>("任務(wù)3 ok"); } }
@RestController public class TaskController { private static Logger logger = LoggerFactory.getLogger(TaskController.class); @Autowired private TaskDemo taskDemo; @GetMapping("/task/test") public String testTask() throws InterruptedException { Future<String> task1 = taskDemo.execute1(); Future<String> task2 = taskDemo.execute2(); Future<String> task3 = taskDemo.execute3(); while (true){ if (task1.isDone() && task2.isDone() && task3.isDone()){ break; } TimeUnit.SECONDS.sleep(1); } logger.info(">>>>>>3個(gè)任務(wù)都處理完成"); return "ok"; } }
執(zhí)行結(jié)果發(fā)現(xiàn),在請(qǐng)求線程里面給用戶提示了3個(gè)任務(wù)都處理完成了。
這段代碼主要改變了什么:
1、把具體任務(wù)返回類型改為了Future類型對(duì)象
2、在調(diào)用任務(wù)時(shí),循環(huán)判斷任務(wù)是否處理完
自定義線程池
說到異步處理,一定要考慮到線程池,什么是線程池,小伙伴可自行網(wǎng)補(bǔ)。@Async的線程池定義比較方便,直接上代碼:
@Configuration public class ThreadPoolConfig { @Bean(name = "taskPool01Executor") public ThreadPoolTaskExecutor getTaskPool01Executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心線程數(shù) taskExecutor.setCorePoolSize(10); //線程池維護(hù)線程的最大數(shù)量,只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過核心線程數(shù)的線程 taskExecutor.setMaxPoolSize(100); //緩存隊(duì)列 taskExecutor.setQueueCapacity(50); //許的空閑時(shí)間,當(dāng)超過了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀 taskExecutor.setKeepAliveSeconds(200); //異步方法內(nèi)部線程名稱 taskExecutor.setThreadNamePrefix("TaskPool-01-"); /** * 當(dāng)線程池的任務(wù)緩存隊(duì)列已滿并且線程池中的線程數(shù)目達(dá)到maximumPoolSize,如果還有任務(wù)到來就會(huì)采取任務(wù)拒絕策略 * 通常有以下四種策略: * ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。 * ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。 * ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程) * ThreadPoolExecutor.CallerRunsPolicy:重試添加當(dāng)前的任務(wù),自動(dòng)重復(fù)調(diào)用 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.initialize(); return taskExecutor; } @Bean(name = "taskPool02Executor") public ThreadPoolTaskExecutor getTaskPool02Executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心線程數(shù) taskExecutor.setCorePoolSize(10); //線程池維護(hù)線程的最大數(shù)量,只有在緩沖隊(duì)列滿了之后才會(huì)申請(qǐng)超過核心線程數(shù)的線程 taskExecutor.setMaxPoolSize(100); //緩存隊(duì)列 taskExecutor.setQueueCapacity(50); //許的空閑時(shí)間,當(dāng)超過了核心線程出之外的線程在空閑時(shí)間到達(dá)之后會(huì)被銷毀 taskExecutor.setKeepAliveSeconds(200); //異步方法內(nèi)部線程名稱 taskExecutor.setThreadNamePrefix("TaskPool-02-"); /** * 當(dāng)線程池的任務(wù)緩存隊(duì)列已滿并且線程池中的線程數(shù)目達(dá)到maximumPoolSize,如果還有任務(wù)到來就會(huì)采取任務(wù)拒絕策略 * 通常有以下四種策略: * ThreadPoolExecutor.AbortPolicy:丟棄任務(wù)并拋出RejectedExecutionException異常。 * ThreadPoolExecutor.DiscardPolicy:也是丟棄任務(wù),但是不拋出異常。 * ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊(duì)列最前面的任務(wù),然后重新嘗試執(zhí)行任務(wù)(重復(fù)此過程) * ThreadPoolExecutor.CallerRunsPolicy:重試添加當(dāng)前的任務(wù),自動(dòng)重復(fù)調(diào)用 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); taskExecutor.setWaitForTasksToCompleteOnShutdown(true); taskExecutor.initialize(); return taskExecutor; } }
定義了2個(gè)線程池Bean
@Service public class TaskDemo { private static Logger logger = LoggerFactory.getLogger(TaskDemo.class); @Async("taskPool01Executor") public Future<String> execute1() { logger.info("處理耗時(shí)任務(wù)1......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)1......結(jié)束"); return new AsyncResult<>("任務(wù)1 ok"); } @Async("taskPool01Executor") public Future<String> execute2() { logger.info("處理耗時(shí)任務(wù)2......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)2......結(jié)束"); return new AsyncResult<>("任務(wù)2 ok"); } @Async("taskPool02Executor") public Future<String> execute3() { logger.info("處理耗時(shí)任務(wù)3......開始"); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } logger.info("處理耗時(shí)任務(wù)3......結(jié)束"); return new AsyncResult<>("任務(wù)3 ok"); } }
@Async(“線程池名稱”),指定value使用自己定義的線程池:
執(zhí)行結(jié)果利用了線程池。
注意事項(xiàng)(一定注意)
在使用@Async注解時(shí),很多小伙伴都會(huì)發(fā)現(xiàn)異步使用失敗。主要原因是異步方法的定義出了問題。
1、異步方法不能使用static修飾
2、異步類沒有使用@Component注解(或其他注解)導(dǎo)致spring無法掃描到異步類
3、異步方法和調(diào)用異步方法的方法不能在同一個(gè)類
4、類中需要使用@Autowired或@Resource等注解自動(dòng)注入,不能自己手動(dòng)new對(duì)象
5、如果使用SpringBoot框架必須在啟動(dòng)類中增加@EnableAsync注解?
到此這篇關(guān)于一文搞懂SpringBoot如何利用@Async實(shí)現(xiàn)異步調(diào)用的文章就介紹到這了,更多相關(guān)SpringBoot @Async異步調(diào)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Springboot參數(shù)校驗(yàn)之分組校驗(yàn)、嵌套校驗(yàn)的實(shí)現(xiàn)
日常開發(fā)中,免不了需要對(duì)請(qǐng)求參數(shù)進(jìn)行校驗(yàn),諸如判空,長度,正則,集合等,復(fù)雜一點(diǎn)的請(qǐng)求參數(shù)可能會(huì)包含嵌套,分組校驗(yàn),本文就詳細(xì)的介紹一下,感興趣的可以了解一下2023-08-08SpringBoot實(shí)現(xiàn)發(fā)送QQ郵件的示例代碼
這篇文章主要介紹了SpringBoot如何實(shí)現(xiàn)發(fā)送QQ郵件功能,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09SpringAOP實(shí)現(xiàn)自定義接口權(quán)限控制
本文主要介紹了SpringAOP實(shí)現(xiàn)自定義接口權(quán)限控制,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-11-11SpringBoot創(chuàng)建RSocket服務(wù)器的全過程記錄
RSocket應(yīng)用層協(xié)議支持 Reactive Streams語義, 例如:用RSocket作為HTTP的一種替代方案。這篇文章主要給大家介紹了關(guān)于SpringBoot創(chuàng)建RSocket服務(wù)器的相關(guān)資料,需要的朋友可以參考下2021-05-05深入理解Java8新特性之Optional容器類的應(yīng)用
Optional<T> 類(java.util.Optional) 是一個(gè)容器類,代表一個(gè)值存在或不存在,原來用 null 表示一個(gè)值不存在,現(xiàn)在 Optional 可以更好的表達(dá)這個(gè)概念。并且可以避免空指針異常,需要的朋友可以參考下本文2021-11-11Java 實(shí)現(xiàn)隨機(jī)驗(yàn)證碼功能簡單實(shí)例
這篇文章主要介紹了Java 實(shí)現(xiàn)隨機(jī)驗(yàn)證碼功能簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-04-04