SpringBoot 非同步程式設計淺談

2023-12-19 06:00:23

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);
    }

第三步:在需要進行非同步執行的地方進行呼叫

asyncTask.Task();

  

4. @Async的原理

  1. 當一個帶有@Async註解的方法被呼叫時,Spring會建立一個非同步代理物件來代理這個方法的呼叫。

  2. 非同步代理物件會將方法呼叫封裝為一個獨立的任務,並將該任務提交給非同步任務執行器。

  3. 非同步任務執行器從執行緒池中獲取一個空閒的執行緒,並將任務分配給該執行緒執行。

  4. 呼叫執行緒立即返回,不會等待非同步任務的執行完成。

  5. 非同步任務在獨立的執行緒中執行,直到任務完成。

  6. 非同步任務執行完成後,可以選擇返回結果或者不返回任何結果。