Spring の TaskExecutor はタスクの送信を抽象化します。一般的な ThreadPoolTaskExecutor は ThreadPoolExecutor をラップする管理対象の JavaBean ですが、プール、キュー、拒否、ライフサイクルの選択がシステム容量を決定します。
エグゼキューターを定義して名前を付ける #
呼び出し元が意図したリソースを選択できるよう、ワークロードに基づいてエグゼキューターに名前を付けます。
@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 は管理対象 Bean として ThreadPoolTaskExecutor を初期化してシャットダウンします。具体型を返すことで、ライフサイクル設定と監視メソッドが保持されます。レイテンシー、ブロッキング、優先度、下流容量の要件が異なるワークロードは、別々のエグゼキューターで分離します。
プール、キュー、飽和動作 #
基盤となるエグゼキューターは、特定の受け入れ順序に従います。
corePoolSizeに達するまでワーカーを作成する。- ワークキューが受け入れる間、新しいタスクをキューに入れる。
- キューが項目を拒否した後にのみ、
maxPoolSizeまでワーカーを作成する。 - プールとキューがともに飽和したときに拒否ポリシーを適用する。
したがって、無制限キューはコアサイズを超える拡張を防ぎ、過負荷を増大するレイテンシーとメモリ使用量に変換します。有限キューは過負荷を可視化します。AbortPolicy は例外で拒否し、CallerRunsPolicy は送信スレッドで実行してバックプレッシャーを適用し、破棄ポリシーは作業を失わせるため、明示的なプロダクト上の判断が必要です。
コアサイズ、最大サイズ、キュー容量、キープアライブは、測定されたタスクのサービス時間、ブロッキング率、到着バースト、下流制限に従う必要があります。大きなプールでもデータベースの接続容量は増やせません。
@Async の選択と失敗
#
@EnableAsync でメソッドインターセプションを有効にします。複数のエグゼキューターがある場合は、名前付きエグゼキューターを選択します。
@Async("reportExecutor")
public CompletableFuture<Report> render(ReportRequest request) {
return CompletableFuture.completedFuture(renderer.render(request));
}アノテーションの修飾子がない場合、インフラストラクチャは一意の TaskExecutor、または taskExecutor という名前の Executor Bean を探します。AsyncConfigurer で明示的なデフォルトを提供できます。プロキシベースの @Async には、他の Spring AOP アドバイスと同じ自己呼び出しの制約があります。
Future または CompletableFuture メソッドの失敗は、その結果を通じて観測します。void の非同期メソッドには結果チャネルがないため、AsyncUncaughtExceptionHandler を設定します。ログ記録だけでは、配信や再試行は保証されません。
コンテキストの伝播 #
エグゼキューターのタスクは別のスレッドで実行されます。スレッドローカルの MDC、セキュリティ、ロケール、観測コンテキストは自動的には移行されません。TaskDecorator は、タスク送信時に選択した状態を取得し、実行前後に復元し、finally でクリアできます。必要なコンテキストだけをコピーしてください。再利用されるワーカーに古い状態や秘密の状態が漏れると、正確性とセキュリティ上の欠陥になります。
正常なシャットダウン #
シャットダウンポリシーはタスクの意味に適合させる必要があります。キューに入った作業を待つかどうかを設定し、有限の終了待機期間を設けます。サービスのシャットダウン時には、まず新しいリクエストの受け付けを停止し、次に受け付けた作業を期限内に完了させ、最後に未完了の作業をキャンセルまたは永続化します。メモリ内のエグゼキューターキューは永続的ではありません。
Spring Boot と仮想スレッド #
Spring Boot 3.2 以降では、次の設定で自動構成された非同期エグゼキューターに Java 21 の仮想スレッドを使用できます。
spring.threads.virtual.enabled=true仮想スレッドはタスクごとに 1 つのブロッキングスレッドを使用するコストを削減しますが、データベース、HTTP サービス、その他の希少なリソースへの同時呼び出しを制限するものではありません。その目的にはセマフォ、接続プール、ワークロード受け入れ制限を使用します。Boot がタスクごとの仮想スレッドエグゼキューターを選択する場合、多くのプールサイズプロパティは適用されません。
JDK 21 から 23 では、モニターを保持したまま、または一部のネイティブコード内でブロックすると、仮想スレッドがキャリアにピン留めされることがあります。JDK 24 の JEP 491 はモニターベースのピン留めを取り除きますが、ネイティブおよび外部関数境界については測定が必要です。仮想スレッドによってすべてのブロッキングが安全になると仮定せず、実際にデプロイされた JDK バージョンを Java Flight Recorder とスレッドダンプで診断します。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)