Spring Boot 進階- 如何使用@Async註解提升API併發

Spring Boot 進階- 如何使用@Async註解提升API併發

首先,我們先聊聊非同步呼叫?那麼什麼是非同步呼叫呢?與非同步呼叫對應的應該是同步呼叫,關於同步我們應該不陌生,就是我們的程式呼叫或者是介面呼叫是按照一定的順序進行執行的,每個過程都是需要等待上一個過程完成之後才執行,不會出現過程之間的相互交替執行的情況。

而非同步呼叫則不同,雖然我們的程式是順序執行的,但是在呼叫的過程中一個過程的執行不需要等待到另一個過程執行完成返回之後就可以執行,這種操作我們就把它稱為是非同步呼叫。

下面我們就先來通過幾個例子來看一下同步呼叫

同步呼叫

第一步、首先我們來開發一個任務執行類,在這個任務執行類中有三個隨機時間的任務等待執行。

@Componentpublic class Task { public static Random random = new Random(); public void doTackOne() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務一執行完成”); } public void doTackTwo() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務二執行完成”); } public void doTackThree() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務三執行完成”); }}

第二步、在Controller類中呼叫對應的任務類,並且按照順序呼叫執行任務

@RestControllerpublic class TaskController { @Autowired private Task task; @GetMapping(“/hello”) public String hello() throws InterruptedException { task。doTackOne(); task。doTackTwo(); task。doTackThree(); return “Task”; }}

執行結果如下,無論如何呼叫介面,這三個任務都是按照程式碼順序執行輸出的。所以這三個任務在這樣的一種執行情況下,他們是同步執行的,也就是說任務一執行完成之後,任務二執行,任務二執行完成之後,才會輪到任務三執行。而這其中,無論任務一需要隨機幾秒的等待時間,任務二都必須完成。

Spring Boot 進階- 如何使用@Async註解提升API併發

非同步呼叫

考慮在上面這種情況下,任務一執行完成的時間是不固定的,也可能1秒執行完成也可能10秒執行完成。如果其中一個任務執行時間較長的話可能會影響到其他任務的執行。其實從邏輯上看,三個任務之間並沒有明確的因果關係,第二個任務的執行並不需要第一個任務執行的返回結果做為依賴。這個時候。我們就可以考慮採用非同步呼叫的方式了,下面我們就可以將任務類程式碼進行修改。

@Componentpublic class Task { public static Random random = new Random(); @Async public void doTackOne() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務一執行完成”); } @Async public void doTackTwo() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務二執行完成”); } @Async public void doTackThree() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); System。out。println(“任務三執行完成”); }}

會看到我們在每個執行任務的方法上都加上了@Async 註解,透過這個註解就可以讓我們的方法執行變成非同步執行。

當然光有@Async註解是不夠的,我們還要在主啟動類上標註@EnableAsync,註解允許應用程式透過@Async 註解來進行非同步執行,當然我們也可以專門寫一個配置類在配置類中新增@EnableAsync註解。

@SpringBootApplication@EnableAsyncpublic class DemoApplication { public static void main(String[] args) { SpringApplication。run(DemoApplication。class, args); }}

Spring Boot 進階- 如何使用@Async註解提升API併發

呼叫之後程式立即返回,並且任務執行的順序也發生了變化。反覆呼叫之後,會發現每次任務的呼叫順序都會發生變化。原因就是任務執行的三個方法都被設定了非同步執行,在程式啟動之後,呼叫方法不會去關心這三個方法如何執行,只關心主程式方法如何執行。所以呼叫之後,就立即會返回結果。

這裡需要注意的一點就是,被@Async註解標註的方法不能是靜態方法。

這裡就一個新的問題出現了,我們怎麼知道非同步呼叫的方法到底有沒有執行呢?或者是非同步呼叫的方法是什麼時候執行完成的呢?

這個時候我們就需要對任務方法進行改進了,

非同步回撥

既然要知道結果,那麼我們在任務執行完成之後進行返回,所以我們就將程式碼改成如下這個樣子來實現。

@Componentpublic class Task { public static Random random = new Random(); @Async public Future doTackOne() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); return new AsyncResult<>(“任務一完成”); } @Async public Future doTackTwo() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); return new AsyncResult<>(“任務二完成”); } @Async public Future doTackThree() throws InterruptedException { TimeUnit。SECONDS。sleep(random。nextInt(10)); return new AsyncResult<>(“任務三完成”); }}

對任務方法完成改造之後,接下來,我們就需要獲取到對應的返回值並且對返回值進行判斷是否任務執行完成。這將如何實現呢?

@RestControllerpublic class TaskController { @Autowired private Task task; @GetMapping(“/hello”) public String hello() throws InterruptedException { Future doTackOne = task。doTackOne(); Future doTackTwo = task。doTackTwo(); Future doTackThree = task。doTackThree(); // 設定自旋等待 while (true){ if (doTackOne。isDone()&&doTackTwo。isDone()&&doTackThree。isDone()){ break; } TimeUnit。SECONDS。sleep(1); } System。out。println(“所有任務完成”); return “Task”; }}

這個時候,我們透過理論計算可以知道,要想三個任務同時完成,那麼它所用的時間應該是任務執行時間最長的哪個任務決定,也就是說如果任務三執行的時間是2秒,任務二執行的時間是3秒,任務一執行的時間是4秒,那麼最終完成所有任務所用的時間應該是4秒左右。而如果採用同步的方式我們可以計算的到完成所有任務的總用時是三者之和,也就是9秒。

從這裡可以知道,採用非同步的方式為整個的過程呼叫節省了5秒中的等待時間。可見非同步呼叫在一些併發專案中確實可以節省不少時間提升介面呼叫效率。