SpringのExecutor設定を完全解説!初心者向けコアプール・キュー・拒否ポリシー入門
生徒
「Spring Bootで非同期処理を設定したいのですが、Executorって何ですか?」
先生
「Executorは非同期タスクを処理するスレッドプールの仕組みですよ。@Asyncやスケジューリングでもよく使われます。」
生徒
「スレッド数やキューの設定もできるんですか?」
先生
「はい、コアプールサイズやキューサイズ、拒否ポリシーも自由に設定できます。それでは、詳しく見ていきましょう!」
1. ExecutorとTaskExecutorとは?
「1. ExecutorとTaskExecutorとは?」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
ExecutorはJavaの標準インターフェースで、非同期にタスクを実行するための基盤です。SpringではこのExecutorを拡張したTaskExecutorを使って、非同期処理やスケジューリング処理を簡単に扱えるようにしています。
Springの非同期処理(@Async)やスケジューラ(@Scheduled)などの内部では、TaskExecutorが使われており、これをカスタマイズすることでスレッドの動作や並列数などを細かく制御することが可能になります。
2. ThreadPoolTaskExecutorの基本構成
SpringではThreadPoolTaskExecutorというクラスを使って、実行スレッドのプール(ThreadPool)を構成できます。以下のようなパラメータを設定することができます。
- corePoolSize:常に確保するスレッド数
- maxPoolSize:最大スレッド数
- queueCapacity:キューの最大数(スレッドがいっぱいの時にここに溜める)
- keepAliveSeconds:アイドルスレッドの終了までの時間
- rejectedExecutionHandler:タスクが溢れた時の処理(拒否ポリシー)
3. ThreadPoolTaskExecutorの設定例
以下は、corePoolSizeやqueueCapacityなどを設定したサンプルコードです。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ExecutorConfig {
@Bean("customTaskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // コアスレッド数
executor.setMaxPoolSize(10); // 最大スレッド数
executor.setQueueCapacity(100); // キューのサイズ
executor.setKeepAliveSeconds(60); // アイドル時間
executor.setThreadNamePrefix("MyExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 拒否ポリシー
executor.initialize();
return executor;
}
}
4. コアプールサイズと最大スレッド数の違い
「4. コアプールサイズと最大スレッド数の違い」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
コアプールサイズは、常に起動しているスレッドの数を指します。リクエストが少なくてもこの数だけスレッドが維持されます。
一方で最大スレッド数は、リクエストが殺到してキューに入りきらなかった場合に、スレッド数を増やして最大この数まで処理できるようにする上限値です。
5. キュー容量の役割と注意点
スレッドがすべて使用中のとき、残りのタスクはキューに保存されます。これがqueueCapacityです。設定値を小さくしすぎると、すぐにRejectedExecutionHandlerが呼ばれる可能性があります。
逆に、大きく設定しすぎるとスレッドが増えずに処理が遅延する場合もあります。適切なバランスが重要です。
6. 拒否ポリシーの種類と使い分け
すべてのスレッドとキューが埋まってしまった場合、次のタスクはRejectedExecutionHandlerによって処理されます。以下のような種類があります。
- AbortPolicy(デフォルト):例外を投げて処理を中断
- CallerRunsPolicy:呼び出し元スレッドで処理を実行
- DiscardPolicy:新しいタスクを破棄(警告なし)
- DiscardOldestPolicy:キューの最も古いタスクを破棄して新しいタスクを追加
例えば、処理を絶対に失敗させたくない場合はCallerRunsPolicyを使うことで、少なくとも呼び出し元スレッドでタスクを実行できます。
7. 実行結果のログ確認例
「7. 実行結果のログ確認例」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
スレッドの名前にsetThreadNamePrefix()で名前をつけておくと、ログ上でどのスレッドで処理されたかが確認しやすくなります。
MyExecutor-1 : タスクA処理中...
MyExecutor-2 : タスクB処理中...
8. スレッドプール設計のベストプラクティス
スレッドプールは単にスレッド数を増やせばよいわけではありません。CPU数やI/O待ちの割合に応じて設計することが大切です。
- CPUバウンドな処理:CPUコア数と同程度
- I/Oバウンドな処理:CPUコア数の2〜4倍
- キューサイズは処理件数の傾向を分析して設定
また、アプリのパフォーマンスに影響を与えるので、定期的にモニタリングして調整することも重要です。
9. Executorの設定と@Asyncの組み合わせ
@Asyncアノテーションを使うときにも、上で定義したExecutorを明示的に指定できます。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class SampleAsyncService {
@Async("customTaskExecutor")
public void asyncTask() {
System.out.println("非同期タスク実行中");
}
}
まとめ
「まとめ」の重要ポイントを、初心者の方にも分かりやすく簡潔に解説します。
Executor設定の全体像を振り返る
今回はSpring BootにおけるExecutor設定について詳しく解説してきました。Executorは非同期処理を実現するための重要な仕組みであり、スレッドプールを適切に管理することでアプリケーションのパフォーマンスや安定性を大きく向上させることができます。特にThreadPoolTaskExecutorを利用することで、スレッド数やキューの動作、拒否ポリシーなどを細かく制御できる点は実務でも非常に重要です。
非同期処理はWebアプリケーション開発やバッチ処理など、さまざまな場面で活用されますが、適切に設定しなければ逆にパフォーマンス低下やリソース枯渇といった問題を引き起こす可能性があります。そのためExecutorの仕組みを正しく理解し、状況に応じて適切な設定を行うことが求められます。
特に重要なのがコアプールサイズと最大スレッド数の関係です。コアプールサイズは常に稼働するスレッド数を表し、最大スレッド数は負荷が増加したときに追加で使用される上限です。このバランスが適切でないと、処理が詰まったり、逆に過剰なスレッド生成によってサーバー負荷が高まることがあります。
キューと拒否ポリシーの重要性
キュー容量の設定も非常に重要なポイントです。キューが小さすぎるとすぐにタスクが溢れてしまい、拒否ポリシーが発動して処理が失敗する可能性があります。一方でキューを大きくしすぎるとスレッドが増えず、結果として処理の遅延につながる場合もあります。適切な値を設定するためには、アプリケーションの処理量やアクセス数を把握することが大切です。
また、拒否ポリシーはシステムの挙動に大きく影響します。例えばAbortPolicyは例外を発生させるため、異常を早期に検知できます。一方でCallerRunsPolicyは呼び出し元で処理を行うため、処理の遅延は発生しますがタスクを失わずに済みます。このように用途に応じて適切なポリシーを選択することが重要です。
Executor設定を活かした実装例
次にExecutorを活用した簡単な非同期処理のサンプルを確認してみましょう。設定したスレッドプールがどのように動作するか理解することができます。
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class AsyncExampleService {
@Async("customTaskExecutor")
public void processTask(String name) {
System.out.println(Thread.currentThread().getName() + " : " + name + " を処理中");
}
}
MyExecutor-1 : タスクA を処理中
MyExecutor-2 : タスクB を処理中
スレッド名を確認することで、複数のタスクが並列で処理されていることが分かります。このようにExecutorを使うことで効率的に処理を分散させることができます。
負荷を意識した設計の考え方
スレッドプール設計では単に数値を設定するだけでなく、実際の負荷や処理内容を考慮することが重要です。CPUを多く使う処理と外部通信を伴う処理では最適なスレッド数が異なります。そのため、実際の運用環境でログやメトリクスを確認しながら調整することが推奨されます。
また、非同期処理は便利である反面、デバッグが難しくなるという特徴もあります。処理の流れが分かりにくくなるため、ログ出力やスレッド名の設定を活用して、どの処理がどのスレッドで実行されているかを把握しやすくすることが重要です。
初心者が押さえておきたいポイント
初心者の方はまずExecutorの基本的な役割を理解し、コアプールサイズ、最大スレッド数、キュー容量の関係をイメージできるようになることが大切です。その上で、実際に小さなサンプルを動かしながら挙動を確認すると理解が深まります。
特にSpring Bootでは@AsyncとExecutorを組み合わせることで簡単に非同期処理を実現できるため、設定と動作の関係をしっかり理解しておくと実務でも役立ちます。安定したシステムを構築するためには、適切なスレッド管理が欠かせません。
生徒
「Executorの設定がパフォーマンスに大きく関係することがよくわかりました」
先生
「スレッド数やキューの設定次第で処理効率が大きく変わるため重要なポイントです」
生徒
「拒否ポリシーもただの設定ではなく意味があるんですね」
先生
「用途に応じて選択することで安全に処理を行うことができます」
生徒
「まずは小さいサンプルで動きを確認してみます」
先生
「実際に動かしながら学ぶことで理解が深まります確実に身につけていきましょう」
Spring FrameworkやThymeleafを使った Webアプリ開発の全体像をやさしく理解したい人には、 この入門書が定番です。
Spring Framework超入門をAmazonで見る※ Amazon広告リンク