Java 非同步處理
在深入探討 Spring @Async 之前,同步、非同步和多線程的概念是必不可少的。假設您已了解這些概念,讓我們透過程式碼來了解純粹的 Java 非同步處理方式。如果您熟悉 Java 線程,則可以跳過此章節。
如果要建立一個接收訊息並簡單輸出的功能,可以使用同步方式,程式碼如上所示。如果將其轉換為多線程非同步方式,則可以編寫以下原始碼。
但是,這種方法無法管理 Thread,因此非常危險。例如,如果同時進行 10,000 次呼叫,則需要在很短的時間內建立 10,000 個 Thread。建立 Thread 的成本不低,因此會對程式效能造成負面影響,甚至可能發生 OOM 錯誤。因此,需要實現 Thread Pool 來管理 Thread,Java 提供了 ExecutorService 類別。
將所有 Thread 的數量限制為 10 個,並且能夠正確地執行我們想要的非同步多線程處理方式。但是,此方法需要對每個想要非同步處理的方法都應用 ExecutorService 的 submit() 方法,因此需要重複修改。也就是說,如果最初希望將同步邏輯編寫的方法更改為非同步,則方法本身的邏輯不可避免地需要更改。
Spring @Async
簡單的方法
在 Application 類別上加上 @EnableAsync 注解,並在想要以非同步方式處理的同步邏輯方法上加上 @Async 注解,就完成了。但是,此方法存在不管理線程的問題。因為 @Async 的預設設定是使用 SimpleAsyncTaskExecutor,它不是線程池,而只是建立線程的角色。
使用線程池的方法
首先,從 Application 類別中移除 @EnableAsync。如果 Application 類別中設定了 @EnableAutoConfiguration 或 @SpringBootApplication,則在執行時期會讀取 SpringAsyncConfig 類別(稍後將建立)中 threadPoolTaskExecutor Bean 的資訊,因為該類別已設定為 @Configuration。
可以設定 core 和 max 大小。此時,預計會先使用 core 大小,如果無法處理更多工作,則會增加到 max 大小,但實際上並非如此。
在內部,會建立大小為 Integer.MAX_VALUE 的 LinkedBlockingQueue,如果 core 大小的線程無法處理工作,則會在佇列中等待。當佇列已滿時,才會建立 max 大小的線程來處理。
如果覺得將佇列大小設定為 Integer.MAX_VALUE 負擔過大,可以設定 queueCapacity。如果按照上述設定,則會先使用 3 個線程進行處理,如果處理速度變慢,則會將工作放入 100 個大小的佇列中,如果還有更多請求,則會建立最多 30 個線程來處理。
完成線程池設定後,只需在加上 @Async 注解的方法中加上 Bean 的名稱即可。
如果想要設定多種類型的線程池,可以建立多個像 threadPoolTaskExecutor() 這樣的 Bean 建立方法,並在設定 @Async 時放入想要的線程池 Bean 即可。
依據回傳型別而有所不同的回傳形式
**沒有回傳值的情況**
需要非同步處理的方法不需要傳遞處理結果的情況。這種情況下,可以將 @Async 注解的回傳型別設定為 void。
**有回傳值的情況**
可以將 Future、ListenableFuture、CompletableFuture 型別用作回傳型別。將非同步方法的回傳形式用 new AsyncResult() 包裹起來即可。
[Future]
future.get() 會透過封鎖來等待請求結果。因此,它成為了非同步封鎖方式,效能不佳。通常不會使用 Future。
[ListenableFuture]
ListenableFuture 可以透過回呼來非封鎖方式處理工作。addCallback() 方法的第一個參數定義工作完成回呼方法,第二個參數定義工作失敗回呼方法。參考一下,由於將線程池的核心線程設定為 3 個,因此可以確認一開始會顯示 3 個「Task Start」訊息。
[CompletableFuture]
雖然僅使用 ListenableFuture 就可以實現非封鎖邏輯,但如果需要在回呼中使用回呼,則會導致非常複雜的程式碼,稱為回呼地獄。
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f152db8-c015-43cd-bf13-85c594f5f218%2FUntitled.png?table=block&id=268ac0bc-ca7b-4bcb-b11b-ac611a5038b2&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1455;" width="2000" height="1455"></span>
當然,本次課程不會詳細介紹 CompletableFuture,因此如果您對處理複雜回呼的程式碼感興趣,請參考以下連結。
可讀性比 ListenableFuture 的回呼定義更好,並且可以完美地執行非封鎖功能。因此,建議在使用 @Async 時,如果需要回傳值,則使用 CompletableFuture。
@Async 的優點
開發人員可以同步方式撰寫方法,如果想要非同步,只需在方法上加上 @Async 注解即可。因此,可以建立易於維護的同步和非同步程式碼。
@Async 的注意事項
為了使用 @Async 功能,需要宣告 @EnableAsync 注解,如果沒有進行其他設定,則會以 Proxy 模式運作。也就是說,使用 @Async 注解運作的非同步方法都會遵循 Spring AOP 的限制。詳細原因請參考「該文章」。
- 即使在 private 方法上加上 @Async,AOP 也不會運作。
- 在同一個物件內的方法之間呼叫時,AOP 不會運作。
评论0