1. 需求背景
當我們需要提高系統的併發效能時,我們可以將耗時的操作非同步執行,從而避免執行緒阻塞,提高系統的併發效能。例如,在處理大量的併發請求時,如果每個請求都是同步阻塞的方式處
理,系統的響應時間會變得很長。而使用非同步程式設計,可以將一些耗時的操作交給其他執行緒去處理,從而釋放主執行緒,提高系統的併發能力。
2. SpringBoot如何實現非同步呼叫
從Spring 3開始,可以透過在方法上標註@Async
註解來實現非同步方法呼叫。這意味著當我們呼叫被@Async
註解修飾的方法時,它會在後臺以非同步方式執行。為了啟用非同步功能,我們需要
一個配置類,並在該類上使用@EnableAsync
註解。這個註解告訴Spring要開啟非同步功能。
3. 非同步呼叫實現步驟
第一步:新建配置類,開啟@Async功能支援
使用@EnableAsync
來開啟非同步任務支援,@EnableAsync
註解可以直接放在SpringBoot啟動類上,也可以單獨放在其他配置類上。這裡選擇使用單獨的配置類SyncConfiguration
。
使用@Async
註解,在預設情況下用的是SimpleAsyncTaskExecutor執行緒池,該執行緒池不是真正意義上的執行緒池。
使用此執行緒池無法實現執行緒重用,每次呼叫都會新建一條執行緒。若系統中不斷的建立執行緒,最終會導致系統佔用記憶體過高,引發OutOfMemoryError
錯誤,所以在使用Spring中的@Async非同步
框架時要自定義執行緒池,替代預設的SimpleAsyncTaskExecutor,這也是自定義配置的意義之一。
@Configuration @EnableAsync public class SyncConfiguration { @Bean(name = "asyncPoolTaskExecutor") public ThreadPoolTaskExecutor executor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); //核心執行緒數,設定核心執行緒數。核心執行緒數是執行緒池中一直保持活動的執行緒數量,即使它們是空閒的。 taskExecutor.setCorePoolSize(10); //設定執行緒池維護執行緒的最大數量。當緩衝佇列已滿並且核心執行緒數的執行緒都在忙碌時,執行緒池會建立新的執行緒,直到達到最大執行緒數。 taskExecutor.setMaxPoolSize(100); //設定緩衝佇列的容量。當所有的核心執行緒都在忙碌時,新的任務將會被放入緩衝佇列中等待執行。 taskExecutor.setQueueCapacity(50); //設定非核心執行緒的空閒時間。當超過核心執行緒數的執行緒在空閒時間達到設定值後,它們將被銷燬,以減少資源的消耗。 taskExecutor.setKeepAliveSeconds(200); //非同步方法內部執行緒名稱 taskExecutor.setThreadNamePrefix("async-"); /** * 當執行緒池的任務快取佇列已滿並且執行緒池中的執行緒數目達到maximumPoolSize,如果還有任務到來就會採取任務拒絕策略 * 通常有以下四種策略: * ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。 * ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。 * ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程) * ThreadPoolExecutor.CallerRunsPolicy:重試新增當前的任務,自動重複呼叫 execute() 方法,直到成功 */ taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); taskExecutor.initialize(); return taskExecutor; } }
注:
Spring提供了多種執行緒池:
-
SimpleAsyncTaskExecutor
:不是真的執行緒池,這個類不重用執行緒,每次呼叫都會建立一個新的執行緒。 -
SyncTaskExecutor
:這個類沒有實現非同步呼叫,只是一個同步操作。只適用於不需要多執行緒的地 -
ConcurrentTaskExecutor
:Executor的適配類,不推薦使用。如果ThreadPoolTaskExecutor不滿足要求時,才用考慮使用這個類 -
ThreadPoolTaskScheduler
:可以使用cron表示式 -
ThreadPoolTaskExecutor
:最常使用,推薦。 其實質是對java.util.concurrent.ThreadPoolExecutor的包裝
第二步:在方法上標記非同步呼叫
在非同步處理的方法上新增@Async
註解,代表該方法為非同步處理。
public class AsyncTask { @Async public void Task() { long t1 = System.currentTimeMillis(); Thread.sleep(5000); long t2 = System.currentTimeMillis(); log.info("task cost {} ms" , t2-t1); }
注:
在非同步程式設計中,如果需要處理帶有返回值的非同步方法(有則繼續瀏覽,無則跳過),Spring提供了java.util.concurrent.Future
介面和java.util.concurrent.CompletableFuture
類來處理非同步任務的返回值。
1. 使用Future
介面:Future
介面表示一個非同步計算的結果。我們可以透過呼叫Future
物件的get()
方法來獲取非同步任務的返回值,但這將阻塞當前執行緒,直到非同步任務完成並返回結果
@Service public class MyService { @Async public Future<String> asyncMethodWithReturnValue() { // 模擬耗時操作 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return new AsyncResult<>("Async method result"); } }
上述示例中,asyncMethodWithReturnValue()
方法使用@Async
註解標記為非同步方法,並返回一個Future<String>
物件。在方法內部,我們使用AsyncResult
類來建立一個包含非同步結果的Future
物件。
在呼叫該非同步方法時,可以使用get()
方法來獲取非同步任務的返回值。但需要注意,get()
方法會阻塞當前執行緒,直到非同步任務執行完成並返回結果。
@Autowired private MyService myService; public void someMethod() { Future<String> futureResult = myService.asyncMethodWithReturnValue(); // 阻塞等待非同步任務完成並獲取返回值 try { String result = futureResult.get(); System.out.println("Async method result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
上述示例中,我們透過呼叫futureResult.get()
方法來獲取非同步任務的返回值。如果非同步任務還未完成,呼叫get()
方法將會阻塞當前執行緒,直到非同步任務完成並返回結果。
2. 使用CompletableFuture
類:CompletableFuture
是Java 8引入的一個強大的非同步程式設計工具,提供了更加靈活和功能豐富的非同步任務處理方式。
@Service public class MyService { @Async public CompletableFuture<String> asyncMethodWithReturnValue() { // 模擬耗時操作 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return CompletableFuture.completedFuture("Async method result"); } }
在上述示例中,asyncMethodWithReturnValue()
方法使用@Async
註解標記為非同步方法,並返回一個CompletableFuture<String>
物件。在方法內部,我們使用CompletableFuture.completedFuture()
方法建立一個包含非同步結果的CompletableFuture
物件。
在呼叫該非同步方法時,可以鏈式呼叫thenApply()
、thenAccept()
等方法來對非同步任務的結果進行處理。
@Autowired private MyService myService; public void someMethod() { CompletableFuture<String> futureResult = myService.asyncMethodWithReturnValue(); futureResult.thenAccept(result -> { System.out.println("Async method result: " + result); }); // 執行其他操作 // 阻塞等待非同步任務完成 futureResult.join(); }
在上述示例中,我們透過呼叫thenAccept()
方法來處理非同步任務的結果,而不需要顯式呼叫get()
方法。thenAccept()
方法接受一個Consumer
函式式介面,用於處理非同步任務的結果。
此外,CompletableFuture
還提供了豐富的方法,例如thenApplyAsync()
、thenCompose()
、thenCombine()
等,用於處理複雜的非同步任務流程。
注:
當使用CompletableFuture
處理非同步任務時,以下是thenApplyAsync()
、thenCompose()
、thenCombine()
和thenAccept()
這四個方法的區別
thenApplyAsync()
和thenAccept()
用於處理非同步任務的結果,並返回一個新的CompletableFuture
或不返回任何結果。thenCompose()
用於處理非同步任務的結果,並返回一個新的CompletableFuture
,該結果是另一個CompletionStage
的結果。thenCombine()
用於組合兩個非同步任務的結果,並應用指定的函式處理結果,並返回一個新的CompletableFuture
。
第三步:在需要進行非同步執行的地方進行呼叫
asyncTask.Task();
4. @Async
的原理
-
當一個帶有
@Async
註解的方法被呼叫時,Spring會建立一個非同步代理物件來代理這個方法的呼叫。 -
非同步代理物件會將方法呼叫封裝為一個獨立的任務,並將該任務提交給非同步任務執行器。
-
非同步任務執行器從執行緒池中獲取一個空閒的執行緒,並將任務分配給該執行緒執行。
-
呼叫執行緒立即返回,不會等待非同步任務的執行完成。
-
非同步任務在獨立的執行緒中執行,直到任務完成。
-
非同步任務執行完成後,可以選擇返回結果或者不返回任何結果。