詳解springboot通過(guò)Async注解實(shí)現(xiàn)異步任務(wù)及回調(diào)的方法
前言
什么是異步調(diào)用?
異步調(diào)用是相對(duì)于同步調(diào)用而言的,同步調(diào)用是指程序按預(yù)定順序一步步執(zhí)行,每一步必須等到上一步執(zhí)行完后才能執(zhí)行,異步調(diào)用則無(wú)需等待上一步程序執(zhí)行完即可執(zhí)行。異步調(diào)用可以減少程序執(zhí)行時(shí)間。
1. 環(huán)境準(zhǔn)備
在 Spring Boot
入口類上配置 @EnableAsync
注解開(kāi)啟異步處理。
創(chuàng)建任務(wù)抽象類 AbstractTask
,并實(shí)現(xiàn)三個(gè)任務(wù)方法 doTaskOne()
,doTaskTwo()
,doTaskThree()
。
public abstract class AbstractTask { private static Random random = new Random(); public void doTaskOne() throws Exception { System.out.println("開(kāi)始做任務(wù)一"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)一,耗時(shí):" + (end - start) + "毫秒"); } public void doTaskTwo() throws Exception { System.out.println("開(kāi)始做任務(wù)二"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)二,耗時(shí):" + (end - start) + "毫秒"); } public void doTaskThree() throws Exception { System.out.println("開(kāi)始做任務(wù)三"); long start = currentTimeMillis(); sleep(random.nextInt(10000)); long end = currentTimeMillis(); System.out.println("完成任務(wù)三,耗時(shí):" + (end - start) + "毫秒"); } }
2. 同步調(diào)用
下面通過(guò)一個(gè)簡(jiǎn)單示例來(lái)直觀的理解什么是同步調(diào)用:
定義 Task
類,繼承 AbstractTask
,三個(gè)處理函數(shù)分別模擬三個(gè)執(zhí)行任務(wù)的操作,操作消耗時(shí)間隨機(jī)?。?code>10 秒內(nèi))。
@Component public class SyncTask extends AbstractTask { }
在 單元測(cè)試 用例中,注入 SyncTask
對(duì)象,并在測(cè)試用例中執(zhí)行 doTaskOne()
,doTaskTwo()
,doTaskThree()
三個(gè)方法。
@RunWith(SpringRunner.class) @SpringBootTest public class TaskTest { @Autowired private SyncTask task; @Test public void testSyncTasks() throws Exception { task.doTaskOne(); task.doTaskTwo(); task.doTaskThree(); } }
執(zhí)行單元測(cè)試,可以看到類似如下輸出:
開(kāi)始做任務(wù)一
完成任務(wù)一,耗時(shí):6720毫秒
開(kāi)始做任務(wù)二
完成任務(wù)二,耗時(shí):6604毫秒
開(kāi)始做任務(wù)三
完成任務(wù)三,耗時(shí):9448毫秒
任務(wù)一、任務(wù)二、任務(wù)三順序的執(zhí)行完了,換言之 doTaskOne()
,doTaskTwo()
,doTaskThree()
三個(gè)方法按調(diào)用順序的先后執(zhí)行完成。
3. 異步調(diào)用
上述的 同步調(diào)用 雖然順利的執(zhí)行完了三個(gè)任務(wù),但是可以看到 執(zhí)行時(shí)間比較長(zhǎng),若這三個(gè)任務(wù)本身之間 不存在依賴關(guān)系,可以 并發(fā)執(zhí)行 的話,同步調(diào)用在 執(zhí)行效率 方面就比較差,可以考慮通過(guò) 異步調(diào)用 的方式來(lái) 并發(fā)執(zhí)行。
- 在Application啟動(dòng)類上面加上@EnableAsync
- 創(chuàng)建
AsyncTask
類,分別在方法上配置@Async
注解,將原來(lái)的 同步方法 變?yōu)?異步方法。
@Component public class AsyncTask extends AbstractTask { @Async public void doTaskOne() throws Exception { super.doTaskOne(); } @Async public void doTaskTwo() throws Exception { super.doTaskTwo(); } @Async public void doTaskThree() throws Exception { super.doTaskThree(); } }
在 單元測(cè)試 用例中,注入 AsyncTask
對(duì)象,并在測(cè)試用例中執(zhí)行 doTaskOne()
,doTaskTwo()
,doTaskThree()
三個(gè)方法。
@Autowired private AsyncTask asyncTask; @Test public void testAsyncTasks() throws Exception { asyncTask.doTaskOne(); asyncTask.doTaskTwo(); asyncTask.doTaskThree(); }
執(zhí)行單元測(cè)試,可以看到類似如下輸出:
開(kāi)始做任務(wù)三
開(kāi)始做任務(wù)一
開(kāi)始做任務(wù)二
如果反復(fù)執(zhí)行單元測(cè)試,可能會(huì)遇到各種不同的結(jié)果,比如:
- 沒(méi)有任何任務(wù)相關(guān)的輸出
- 有部分任務(wù)相關(guān)的輸出
- 亂序的任務(wù)相關(guān)的輸出
原因是目前 doTaskOne()
,doTaskTwo()
,doTaskThree()
這三個(gè)方法已經(jīng) 異步并發(fā)執(zhí)行 了。主程序在 異步調(diào)用 之后,主程序并不會(huì)理會(huì)這三個(gè)函數(shù)是否執(zhí)行完成了,由于沒(méi)有其他需要執(zhí)行的內(nèi)容,所以程序就 自動(dòng)結(jié)束 了,導(dǎo)致了任務(wù) 不完整 或是 沒(méi)有輸出 相關(guān)內(nèi)容的情況。
注意:@Async所修飾的函數(shù)不要定義為static類型,這樣異步調(diào)用不會(huì)生效。
4. 異步回調(diào)
為了讓 doTaskOne()
,doTaskTwo()
,doTaskThree()
能正常結(jié)束,假設(shè)我們需要統(tǒng)計(jì)一下三個(gè)任務(wù) 并發(fā)執(zhí)行 共耗時(shí)多少,這就需要等到上述三個(gè)函數(shù)都完成動(dòng)用之后記錄時(shí)間,并計(jì)算結(jié)果。
那么我們?nèi)绾闻袛嗌鲜鋈齻€(gè) 異步調(diào)用 是否已經(jīng)執(zhí)行完成呢?我們需要使用 Future<T>
來(lái)返回 異步調(diào)用 的 結(jié)果。
- 創(chuàng)建
AsyncCallBackTask
類,聲明doTaskOneCallback()
,doTaskTwoCallback()
,doTaskThreeCallback()
三個(gè)方法,對(duì)原有的三個(gè)方法進(jìn)行包裝。
@Component public class AsyncCallBackTask extends AbstractTask { @Async public Future<String> doTaskOneCallback() throws Exception { super.doTaskOne(); return new AsyncResult<>("任務(wù)一完成"); } @Async public Future<String> doTaskTwoCallback() throws Exception { super.doTaskTwo(); return new AsyncResult<>("任務(wù)二完成"); } @Async public Future<String> doTaskThreeCallback() throws Exception { super.doTaskThree(); return new AsyncResult<>("任務(wù)三完成"); } }
在 單元測(cè)試 用例中,注入 AsyncCallBackTask
對(duì)象,并在測(cè)試用例中執(zhí)行 doTaskOneCallback()
,doTaskTwoCallback()
,doTaskThreeCallback()
三個(gè)方法。循環(huán)調(diào)用 Future
的 isDone()
方法等待三個(gè) 并發(fā)任務(wù) 執(zhí)行完成,記錄最終執(zhí)行時(shí)間。
@Autowired private AsyncCallBackTask asyncCallBackTask; @Test public void testAsyncCallbackTask() throws Exception { long start = currentTimeMillis(); Future<String> task1 = asyncCallBackTask.doTaskOneCallback(); Future<String> task2 = asyncCallBackTask.doTaskTwoCallback(); Future<String> task3 = asyncCallBackTask.doTaskThreeCallback(); // 三個(gè)任務(wù)都調(diào)用完成,退出循環(huán)等待 while (!task1.isDone() || !task2.isDone() || !task3.isDone()) { sleep(1000); } long end = currentTimeMillis(); System.out.println("任務(wù)全部完成,總耗時(shí):" + (end - start) + "毫秒"); }
看看都做了哪些改變:
- 在測(cè)試用例一開(kāi)始記錄開(kāi)始時(shí)間;
- 在調(diào)用三個(gè)異步函數(shù)的時(shí)候,返回Future類型的結(jié)果對(duì)象;
- 在調(diào)用完三個(gè)異步函數(shù)之后,開(kāi)啟一個(gè)循環(huán),根據(jù)返回的Future對(duì)象來(lái)判斷三個(gè)異步函數(shù)是否都結(jié)束了。若都結(jié)束,就結(jié)束循環(huán);若沒(méi)有都結(jié)束,就等1秒后再判斷。
- 跳出循環(huán)之后,根據(jù)結(jié)束時(shí)間 - 開(kāi)始時(shí)間,計(jì)算出三個(gè)任務(wù)并發(fā)執(zhí)行的總耗時(shí)。
執(zhí)行一下上述的單元測(cè)試,可以看到如下結(jié)果:
開(kāi)始做任務(wù)三
開(kāi)始做任務(wù)一
開(kāi)始做任務(wù)二
完成任務(wù)二,耗時(shí):2572毫秒
完成任務(wù)一,耗時(shí):7333毫秒
完成任務(wù)三,耗時(shí):7647毫秒
任務(wù)全部完成,總耗時(shí):8013毫秒
到此這篇關(guān)于springboot通過(guò)Async注解實(shí)現(xiàn)異步任務(wù)及回調(diào)的文章就介紹到這了,更多相關(guān)springboot Async注解異步任務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java?Springboot對(duì)接開(kāi)發(fā)微信支付詳細(xì)流程
- springboot使用com.github.binarywang包實(shí)現(xiàn)微信網(wǎng)頁(yè)上的支付和退款
- UniApp?+?SpringBoot?實(shí)現(xiàn)微信支付和退款功能
- springboot對(duì)接微信支付的完整流程(附前后端代碼)
- SpringBoot + 微信公眾號(hào)JSAPI支付功能的實(shí)現(xiàn)
- SpringBoot微信掃碼支付的實(shí)現(xiàn)示例
- SpringCloud解決Feign異步回調(diào)問(wèn)題(SpringBoot+Async+Future實(shí)現(xiàn))
- SpringBoot實(shí)現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)(商戶參數(shù)獲取)
相關(guān)文章
淺談為什么阿里巴巴要禁用Executors創(chuàng)建線程池
這篇文章主要介紹了淺談為什么阿里巴巴要禁用Executors創(chuàng)建線程池,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02Java 中解決Unsupported major.minor version 51.0的問(wèn)題
本文主要介紹解決Unsupported major.minor version 51.0的問(wèn)題, 這里給大家整理了詳細(xì)資料,有需要的小伙伴可以參考下2016-08-08MybatisPlus:使用SQL保留字(關(guān)鍵字)的操作
這篇文章主要介紹了MybatisPlus:使用SQL保留字(關(guān)鍵字)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11springboot結(jié)合ehcache防止惡意刷新請(qǐng)求的實(shí)現(xiàn)
這篇文章主要介紹了springboot結(jié)合ehcache防止惡意刷新請(qǐng)求的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12SpringBoot Knife4j在線API文檔框架基本使用
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強(qiáng)解決方案,這篇文章主要介紹了SpringBoot中使用Knife4J在線API文檔框架,需要的朋友可以參考下2022-12-12