スレッドプールの仕組み — 2人の作業係に4件の仕事を渡す
仕事と作業係を、分けて考える
まずは、2つのワーカーで4件を処理する
スレッドプールは、仕事を渡す側と、実行するスレッドの管理を分ける仕組みです。画像作成のような仕事を、決めた人数の作業係に渡すイメージで見てみましょう。
JDK 25などが使えるなら、新しいフォルダーにPoolDemo.javaをUTF-8で保存します。ここでは実際の画像処理ではなく、仕事名を表示する小さな例です。
import java.util.ArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class PoolDemo {
public static void main(String[] args) throws Exception {
try (var pool = Executors.newFixedThreadPool(2)) {
var results = new ArrayList<Future<String>>();
for (int i = 1; i <= 4; i++) {
final int job = i;
results.add(pool.submit(() ->
"job " + job + " / "
+ Thread.currentThread().getName()
));
}
for (var result : results) {
System.out.println(result.get());
}
}
}
}
javac -encoding UTF-8 PoolDemo.java
java PoolDemo
job 1〜4と、pool-1-thread-1などのワーカー名が出ます。同じ名前が複数の仕事に現れれば、再利用が見えます。どのワーカーが何件担当するかは固定ではありません。 小さな仕事なので、2つが常に同じ量を担当する保証もありません。
get()で結果を読む順序は提出順ですが、内部で実行する順番や完了順とは別です。tryを抜けるとExecutorServiceを閉じ、仕事の終了を待ちます。
係の数だけでなく、待っている仕事も見る
newFixedThreadPoolのキューは無制限です。この例は4件だけですが、無制限に送信し続ければ、安全な上限になるわけではありません。
本番では、次をセットで考えます。
- 同時に処理できる仕事の数。
- 待たせる仕事の容量と、受付を止める条件。
- 通信のタイムアウトと、仕事全体の期限。
- 失敗・拒否したときの応答、取り消しと終了時の扱い。
JavaのThreadPoolExecutorにはcorePoolSize、maximumPoolSize、キュー、keepAliveTimeなどがあります。コアスレッドの事前起動やタイムアウトにも設定があり、「最低人数が常に作成済み」とは限りません。満杯になったときは例外、呼び出し側での実行など、拒否方針も選びます。
もう少し詳しく:待ち方と仮想スレッド
同じプール内で、実行枠を占めた仕事が、まだキューにいる別の仕事を待つ設計は避けます。処理の分離や非同期化を検討するときも、接続先の容量・ロック・依存関係を合わせて見ます。
Java 21で正式化された仮想スレッドは、仕事ごとに作る方針です。仮想スレッドをプールして数を絞る代わりに、DBなどの資源へのアクセスをセマフォ等で制限します。メモリや接続数の限界がなくなるわけではありません。
Java 24ではsynchronizedに起因するピン留めが改善されましたが、ネイティブコード等の条件は残ります。言語・ランタイムによるスケジューリングと、アプリが作る仕事のプールは分けて考えましょう。
覚え方と次の一歩
「スレッドプール」って出てきたら「仕事を渡し、作業係を使い回す仕組み」と思えばだいたいOK!
OSのプロセス管理やサーキットブレーカーと、実行枠や待ち続ける問題の関係を比べられます。
参考資料
- Java SE 25:Executors — 固定プールの共有無制限キューと各実行方式
- Java SE 25:ThreadPoolExecutor — ワーカー生成・上限・キュー・拒否方針
- Oracle JDK 25:Virtual Threads — 仮想スレッドの用途、プールしない方針と資源制御
- OpenJDK:JEP 491 — Java 24のsynchronizedによるピン留め改善と残る条件
- Java SE 25:ForkJoinPool — ワークスティーリングとブロッキング処理の条件