詳解springboot通過Async注解實現異步任務及回調的方法
前言
什么是異步調用?
異步調用是相對于同步調用而言的,同步調用是指程序按預定順序一步步執(zhí)行,每一步必須等到上一步執(zhí)行完后才能執(zhí)行,異步調用則無需等待上一步程序執(zhí)行完即可執(zhí)行。異步調用可以減少程序執(zhí)行時間。

1. 環(huán)境準備

在 Spring Boot 入口類上配置 @EnableAsync 注解開啟異步處理。
創(chuàng)建任務抽象類 AbstractTask,并實現三個任務方法 doTaskOne(),doTaskTwo(),doTaskThree()。
public abstract class AbstractTask {
private static Random random = new Random();
public void doTaskOne() throws Exception {
System.out.println("開始做任務一");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任務一,耗時:" + (end - start) + "毫秒");
}
public void doTaskTwo() throws Exception {
System.out.println("開始做任務二");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任務二,耗時:" + (end - start) + "毫秒");
}
public void doTaskThree() throws Exception {
System.out.println("開始做任務三");
long start = currentTimeMillis();
sleep(random.nextInt(10000));
long end = currentTimeMillis();
System.out.println("完成任務三,耗時:" + (end - start) + "毫秒");
}
}
2. 同步調用
下面通過一個簡單示例來直觀的理解什么是同步調用:
定義 Task 類,繼承 AbstractTask,三個處理函數分別模擬三個執(zhí)行任務的操作,操作消耗時間隨機?。?code>10 秒內)。
@Component
public class SyncTask extends AbstractTask {
}
在 單元測試 用例中,注入 SyncTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個方法。
@RunWith(SpringRunner.class)
@SpringBootTest
public class TaskTest {
@Autowired
private SyncTask task;
@Test
public void testSyncTasks() throws Exception {
task.doTaskOne();
task.doTaskTwo();
task.doTaskThree();
}
}執(zhí)行單元測試,可以看到類似如下輸出:
開始做任務一
完成任務一,耗時:6720毫秒
開始做任務二
完成任務二,耗時:6604毫秒
開始做任務三
完成任務三,耗時:9448毫秒

任務一、任務二、任務三順序的執(zhí)行完了,換言之 doTaskOne(),doTaskTwo(),doTaskThree() 三個方法按調用順序的先后執(zhí)行完成。
3. 異步調用
上述的 同步調用 雖然順利的執(zhí)行完了三個任務,但是可以看到 執(zhí)行時間比較長,若這三個任務本身之間 不存在依賴關系,可以 并發(fā)執(zhí)行 的話,同步調用在 執(zhí)行效率 方面就比較差,可以考慮通過 異步調用 的方式來 并發(fā)執(zhí)行。
- 在Application啟動類上面加上@EnableAsync
- 創(chuàng)建
AsyncTask類,分別在方法上配置@Async注解,將原來的 同步方法 變?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();
}
}
在 單元測試 用例中,注入 AsyncTask 對象,并在測試用例中執(zhí)行 doTaskOne(),doTaskTwo(),doTaskThree() 三個方法。
@Autowired
private AsyncTask asyncTask;
@Test
public void testAsyncTasks() throws Exception {
asyncTask.doTaskOne();
asyncTask.doTaskTwo();
asyncTask.doTaskThree();
}
執(zhí)行單元測試,可以看到類似如下輸出:
開始做任務三
開始做任務一
開始做任務二
如果反復執(zhí)行單元測試,可能會遇到各種不同的結果,比如:
- 沒有任何任務相關的輸出
- 有部分任務相關的輸出
- 亂序的任務相關的輸出

原因是目前 doTaskOne(),doTaskTwo(),doTaskThree() 這三個方法已經 異步并發(fā)執(zhí)行 了。主程序在 異步調用 之后,主程序并不會理會這三個函數是否執(zhí)行完成了,由于沒有其他需要執(zhí)行的內容,所以程序就 自動結束 了,導致了任務 不完整 或是 沒有輸出 相關內容的情況。
注意:@Async所修飾的函數不要定義為static類型,這樣異步調用不會生效。
4. 異步回調
為了讓 doTaskOne(),doTaskTwo(),doTaskThree() 能正常結束,假設我們需要統(tǒng)計一下三個任務 并發(fā)執(zhí)行 共耗時多少,這就需要等到上述三個函數都完成動用之后記錄時間,并計算結果。
那么我們如何判斷上述三個 異步調用 是否已經執(zhí)行完成呢?我們需要使用 Future<T> 來返回 異步調用 的 結果。
- 創(chuàng)建
AsyncCallBackTask類,聲明doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback()三個方法,對原有的三個方法進行包裝。
@Component
public class AsyncCallBackTask extends AbstractTask {
@Async
public Future<String> doTaskOneCallback() throws Exception {
super.doTaskOne();
return new AsyncResult<>("任務一完成");
}
@Async
public Future<String> doTaskTwoCallback() throws Exception {
super.doTaskTwo();
return new AsyncResult<>("任務二完成");
}
@Async
public Future<String> doTaskThreeCallback() throws Exception {
super.doTaskThree();
return new AsyncResult<>("任務三完成");
}
}
在 單元測試 用例中,注入 AsyncCallBackTask 對象,并在測試用例中執(zhí)行 doTaskOneCallback(),doTaskTwoCallback(),doTaskThreeCallback() 三個方法。循環(huán)調用 Future 的 isDone() 方法等待三個 并發(fā)任務 執(zhí)行完成,記錄最終執(zhí)行時間。
@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();
// 三個任務都調用完成,退出循環(huán)等待
while (!task1.isDone() || !task2.isDone() || !task3.isDone()) {
sleep(1000);
}
long end = currentTimeMillis();
System.out.println("任務全部完成,總耗時:" + (end - start) + "毫秒");
}看看都做了哪些改變:
- 在測試用例一開始記錄開始時間;
- 在調用三個異步函數的時候,返回Future類型的結果對象;
- 在調用完三個異步函數之后,開啟一個循環(huán),根據返回的Future對象來判斷三個異步函數是否都結束了。若都結束,就結束循環(huán);若沒有都結束,就等1秒后再判斷。
- 跳出循環(huán)之后,根據結束時間 - 開始時間,計算出三個任務并發(fā)執(zhí)行的總耗時。

執(zhí)行一下上述的單元測試,可以看到如下結果:
開始做任務三
開始做任務一
開始做任務二
完成任務二,耗時:2572毫秒
完成任務一,耗時:7333毫秒
完成任務三,耗時:7647毫秒
任務全部完成,總耗時:8013毫秒
到此這篇關于springboot通過Async注解實現異步任務及回調的文章就介紹到這了,更多相關springboot Async注解異步任務內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
淺談為什么阿里巴巴要禁用Executors創(chuàng)建線程池
這篇文章主要介紹了淺談為什么阿里巴巴要禁用Executors創(chuàng)建線程池,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
Java 中解決Unsupported major.minor version 51.0的問題
本文主要介紹解決Unsupported major.minor version 51.0的問題, 這里給大家整理了詳細資料,有需要的小伙伴可以參考下2016-08-08
springboot結合ehcache防止惡意刷新請求的實現
這篇文章主要介紹了springboot結合ehcache防止惡意刷新請求的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
SpringBoot Knife4j在線API文檔框架基本使用
knife4j是為Java MVC框架集成Swagger生成Api文檔的增強解決方案,這篇文章主要介紹了SpringBoot中使用Knife4J在線API文檔框架,需要的朋友可以參考下2022-12-12

