導入:なぜCountDownLatchが重要なのか
Javaで複数のスレッドを並行して動かす際、ある特定のタスクが終わるまでメイン処理を待たせたい、あるいは複数のスレッドを一斉に開始させたいという場面はよくあります。単純なスレッドのjoin()だけでは管理が煩雑になりがちですが、java.util.concurrentパッケージのCountDownLatchを使うことで、スレッド間の同期を非常に直感的かつ安全に制御できます。これは非同期処理や並列タスクの完了を待機するための強力な同期ツールです。
基礎知識:CountDownLatchの仕組み
CountDownLatchは、「カウンター」を保持する同期支援クラスです。
1. 初期値の設定: インスタンス生成時に、待機させる回数(カウント)を指定します。
2. await()メソッド: カウントが0になるまで、呼び出したスレッドを待機(ブロック)させます。
3. countDown()メソッド: カウントを1減らします。0になった瞬間に、await()で待機していたスレッドが解放されます。
一度カウントが0になると再利用できないという特徴がありますが、その分、特定のイベントの「完了待ち」用途では非常に軽量で扱いやすいのが特徴です。
実装と解決策
例えば、複数のAPIからデータを並行して取得し、すべて揃ってから集計処理を行いたい場合に最適です。ExecutorServiceやCompletableFutureと組み合わせることで、効率的な非同期システムが構築できます。
サンプルプログラム
以下のコードは、3つの並行タスクがすべて完了するまでメインスレッドが待機する例です。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LatchExample {
public static void main(String[] args) throws InterruptedException {
int taskCount = 3;
// カウントダウンの初期値を3に設定
CountDownLatch latch = new CountDownLatch(taskCount);
ExecutorService executor = Executors.newFixedThreadPool(taskCount);
for (int i = 1; i <= taskCount; i++) {
int taskId = i;
executor.submit(() -> {
try {
System.out.println("タスク " + taskId + " を実行中...");
Thread.sleep(1000 taskId); // 処理をシミュレート
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
System.out.println("タスク " + taskId + " 完了");
// カウントを1減らす
latch.countDown();
}
});
}
// カウントが0になるまでメインスレッドをブロック
System.out.println("すべてのタスクの終了を待機します...");
latch.await();
System.out.println("全タスク終了!メイン処理を続行します。");
executor.shutdown();
}
}
応用・注意点:現場で役立つアドバイス
現場でCountDownLatchを使用する際に注意すべき点がいくつかあります。
1. 例外発生時のfinally活用: 上記サンプルコードのように、必ずfinallyブロック内でcountDown()を呼び出してください。タスク内で例外が発生しても、確実にカウントダウンを行わないと、メインスレッドが永久に停止(デッドロック状態)してしまいます。
2. 仮想スレッド(Virtual Threads)との親和性: Java 21以降の仮想スレッドを使用する場合もCountDownLatchは有効ですが、Structured Concurrency(構造化並行処理)のAPI(StructuredTaskScopeなど)が利用可能であれば、そちらを優先的に検討してください。構造化並行処理の方が、エラー時のキャンセル処理などをより安全に自動化できるためです。
3. 再利用の不可: 一度カウントが0になったラッチをリセットして使うことはできません。もし繰り返し同期を行いたい場合は、CyclicBarrierやPhaserの利用を検討しましょう。
CountDownLatchを正しく使いこなして、堅牢で効率的な並行処理システムを構築してください。

コメント