Spring 執行緒池

最後更新: 2023-01-02 16:27:18 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

Spring 的 TaskExecutor 抽象化工作提交。常見的 ThreadPoolTaskExecutor 是受管理的 JavaBean 封裝器,包裝 ThreadPoolExecutor;其執行緒池、佇列、拒絕與生命週期選擇仍決定系統容量。

定義與命名執行器 #

請依工作負載命名執行器,讓呼叫者選取預期的資源:

java
@Bean("reportExecutor")
ThreadPoolTaskExecutor reportExecutor() {
    var executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(8);
    executor.setMaxPoolSize(24);
    executor.setQueueCapacity(100);
    executor.setKeepAliveSeconds(30);
    executor.setThreadNamePrefix("report-");
    executor.setRejectedExecutionHandler(
        new ThreadPoolExecutor.CallerRunsPolicy());
    return executor;
}

Spring 會將 ThreadPoolTaskExecutor 作為受管理 bean 初始化並關閉。回傳其具體型別可保留生命週期設定與監控方法。不同延遲、阻塞、優先順序或下游容量需求的工作負載應使用分開的執行器隔離。

執行緒池、佇列與飽和行為 #

底層執行器遵循特定的接納順序:

  1. 建立工作者,直到達到 corePoolSize。
  2. 工作佇列接受新工作時,將工作排入佇列。
  3. 只有在佇列拒絕項目後,才建立工作者,直到達到 maxPoolSize。
  4. 執行緒池與佇列都飽和時,套用拒絕原則。

因此,無界佇列會阻止大小超過核心值,並將過載轉化為持續增加的延遲與記憶體使用。有界佇列會使過載可觀察。AbortPolicy 以例外拒絕,CallerRunsPolicy 在提交執行緒上執行工作以施加反壓,而捨棄原則會遺失工作,需要明確的產品決策。

核心/最大大小、佇列容量與存活時間必須依據已量測的工作服務時間、阻塞比例、到達突發與下游限制。較大的執行緒池無法增加資料庫的連線容量。

@Async 選取與失敗 #

使用 @EnableAsync 啟用方法攔截。應用程式有多個執行器時,請選取具名執行器:

java
@Async("reportExecutor")
public CompletableFuture<Report> render(ReportRequest request) {
    return CompletableFuture.completedFuture(renderer.render(request));
}

沒有註解限定詞時,基礎結構會尋找唯一的 TaskExecutor,或名為 taskExecutor 的 Executor bean;AsyncConfigurer 可以提供明確的預設值。以 Proxy 為基礎的 @Async 具有與其他 Spring AOP 建議相同的自我呼叫限制。

Future 或 CompletableFuture 方法的失敗會透過該結果觀察。非同步 void 方法沒有結果通道,因此請設定 AsyncUncaughtExceptionHandler;單靠記錄不是傳遞或重試保證。

內容傳遞 #

執行器工作會在另一個執行緒上執行。執行緒區域的 MDC、安全性、地區設定與觀測內容不會自動傳遞。TaskDecorator 可以在提交工作時擷取選定狀態,在執行周圍恢復它,並在 finally 中清除。只複製必要內容,因為過時或秘密狀態洩漏至重複使用的工作者是正確性與安全性缺陷。

正常關閉 #

關閉原則必須符合工作語意。設定執行器是否等待佇列工作,並設定有限的等待終止期間。服務關閉期間,先停止接受新請求,再讓已接受工作在期限內完成,最後取消或持久化未完成工作。記憶體內執行器佇列不具持久性。

Spring Boot 與虛擬執行緒 #

Spring Boot 3.2 及更新版本可以使用 Java 21 虛擬執行緒作為自動設定的非同步執行器:

properties
spring.threads.virtual.enabled=true

虛擬執行緒降低每項工作使用一個阻塞執行緒的成本,但不會限制對資料庫、HTTP 服務或其他稀缺資源的並行呼叫。請使用 semaphore、連線池或工作負載接納限制來達成該目的。Boot 選取每項工作一個虛擬執行緒的執行器時,許多執行緒池大小屬性不適用。

在 JDK 21 至 23 中,持有監視器或在某些原生程式碼中阻塞,可能將虛擬執行緒固定在其載體上。JDK 24 的 JEP 491 移除了以監視器為基礎的固定,但原生與外部函式邊界仍需要測量。請使用 Java Flight Recorder 與執行緒傾印診斷實際部署的 JDK 版本,而不要假設虛擬執行緒會使所有阻塞都無害。

主要參考資料 #

評論

在 GitHub 上閱讀或評論(發表評論需要登入)