Java : CountDownLatch (同期) - API使用例

CountDownLatch (Java SE 18 & JDK 18) の使用例まとめです。
だいたいのメソッドを網羅済みです。
API仕様のおともにどうぞ。


概要

ほかのスレッドで実行中の操作セットが完了するまで、1つ以上のスレッドを待機可能にする同期化支援機能です。

クラス構成

CountDownLatch は、スレッドの処理をブロック/リセットするためのクラスです。

大まかな流れは…

  1. 指定したカウントを持つ CountDownLatch を生成
  2. メインとなるスレッドでは、await メソッドでカウントが 0 になるまで待機
  3. 別のスレッドから countDown メソッドで、カウントを 1 減らす
  4. カウントが 0 になったら、await で待機していたスレッドが復帰

となります。

シーケンス図

コード例です。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedSec = () -> (System.nanoTime() - current) / 1000000000.0;

final var executorService = Executors.newFixedThreadPool(2);

try {
    final var latch = new CountDownLatch(2);
    System.out.println("Main Thread : latch count = " + latch.getCount());

    executorService.submit(() -> {
        try {
            System.out.printf("  Thread A : start : %f sec.%n", elapsedSec.getAsDouble());
            TimeUnit.SECONDS.sleep(1);
            System.out.printf("  Thread A : end   : %f sec.%n", elapsedSec.getAsDouble());
        } finally {
            latch.countDown();
            System.out.println("  Thread A : latch count = " + latch.getCount());
        }
        return null;
    });

    executorService.submit(() -> {
        try {
            System.out.printf("  Thread B : start : %f sec.%n", elapsedSec.getAsDouble());
            TimeUnit.SECONDS.sleep(2);
            System.out.printf("  Thread B : end   : %f sec.%n", elapsedSec.getAsDouble());
        } finally {
            latch.countDown();
            System.out.println("  Thread B : latch count = " + latch.getCount());
        }
        return null;
    });

    System.out.printf("Main Thread : await start : %f sec.%n", elapsedSec.getAsDouble());
    latch.await();
    System.out.printf("Main Thread : await end : %f sec.%n", elapsedSec.getAsDouble());

} finally {
    executorService.shutdown();
}

final var term = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + term);

// 結果
// ↓
//Main Thread : latch count = 2
//  Thread A : start : 0.004276 sec.
//  Thread B : start : 0.004439 sec.
//Main Thread : await start : 0.004305 sec.
//  Thread A : end   : 1.006230 sec.
//  Thread A : latch count = 1
//  Thread B : end   : 2.007936 sec.
//  Thread B : latch count = 0
//Main Thread : await end : 2.008358 sec.
//term : true

コーディングするときの注意

  • countDown が呼び出されないと、await がずっと復帰されないことになります。
    例えば、例外が発生して countDown の呼び出しがスキップされてしまった、ということが起きないように注意しましょう。

関連記事:synchronizedの多いコードは危険信号


コンストラクタ

CountDownLatch (int count)

指定されたカウントで初期化されたCountDownLatchを構築します。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedSec = () -> (System.nanoTime() - current) / 1000000000.0;

final var executorService = Executors.newSingleThreadExecutor();

try {
    final var latch = new CountDownLatch(1);

    executorService.submit(() -> {
        try {
            System.out.printf("  sleep start : %f sec.%n", elapsedSec.getAsDouble());
            TimeUnit.SECONDS.sleep(1);
            System.out.printf("  sleep end   : %f sec.%n", elapsedSec.getAsDouble());
        } finally {
            latch.countDown();
        }
        return null;
    });

    System.out.printf("await start : %f sec.%n", elapsedSec.getAsDouble());
    latch.await();
    System.out.printf("await end   : %f sec.%n", elapsedSec.getAsDouble());

} finally {
    executorService.shutdown();
}

final var term = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + term);

// 結果
// ↓
//await start : 0.001962 sec.
//  sleep start : 0.002234 sec.
//  sleep end   : 1.015228 sec.
//await end   : 1.015626 sec.
//term : true

メソッド

void await ()

スレッドで割り込みが発生しないかぎり、ラッチのカウント・ダウンがゼロになるまで現在のスレッドを待機させます。

通常の使用例は、概要 および コンストラクタ のコード例をご参照ください。
以下は、スレッド割り込みを発生させた例です。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedSec = () -> (System.nanoTime() - current) / 1000000000.0;

final var executorService = Executors.newFixedThreadPool(2);

try {
    final var latch = new CountDownLatch(1);

    final var future = executorService.submit(() -> {
        try {
            System.out.printf("Thread A : start : %f sec.%n", elapsedSec.getAsDouble());
            latch.await();

        } catch (InterruptedException e) {
            System.out.println("Thread A : InterruptedException!");
        } finally {
            System.out.printf("Thread A : end : %f sec.%n", elapsedSec.getAsDouble());
        }
    });

    executorService.submit(() -> {
        System.out.printf("Thread B : sleep start : %f sec.%n", elapsedSec.getAsDouble());
        TimeUnit.SECONDS.sleep(5);
        System.out.printf("Thread B : sleep end   : %f sec.%n", elapsedSec.getAsDouble());

        // 割り込みを発生させます。
        final var ret = future.cancel(true);
        System.out.println("Thread B : cancel : " + ret);

        return null;
    });

} finally {
    executorService.shutdown();
}

final var term = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + term);

// 結果
// ↓
//Thread A : start : 0.002028 sec.
//Thread B : sleep start : 0.002209 sec.
//Thread B : sleep end   : 5.012301 sec.
//Thread A : InterruptedException!
//Thread A : end : 5.013477 sec.
//Thread B : cancel : true
//term : true

boolean await (long timeout, TimeUnit unit)

スレッドで割り込みが発生するか、指定された待機時間が経過しないかぎり、ラッチのカウント・ダウンがゼロになるまで現在のスレッドを待機させます。

スレッドで割り込みが発生するケースは await() と同等となります。
API使用例はそちらもご参照ください。

カウントがゼロになる例です。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedSec = () -> (System.nanoTime() - current) / 1000000000.0;

final var executorService = Executors.newSingleThreadExecutor();

try {
    final var latch = new CountDownLatch(1);

    executorService.submit(() -> {
        try {
            System.out.printf("Thread A : sleep start : %f sec.%n", elapsedSec.getAsDouble());
            TimeUnit.SECONDS.sleep(2);
            System.out.printf("Thread A : sleep end   : %f sec.%n", elapsedSec.getAsDouble());
        } finally {
            latch.countDown();
        }
        return null;
    });

    final var ret = latch.await(5, TimeUnit.SECONDS);
    System.out.printf("await ret = %b : %f sec.%n", ret, elapsedSec.getAsDouble());

} finally {
    executorService.shutdown();
}

final var term = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + term);

// 結果
// ↓
//Thread A : sleep start : 0.002863 sec.
//Thread A : sleep end   : 2.005293 sec.
//await ret = true : 2.005695 sec.
//term : true

タイムアウトが発生する例です。

// 基準となる時刻
final long current = System.nanoTime();

// 基準となる時刻からの差分を秒として取得
final DoubleSupplier elapsedSec = () -> (System.nanoTime() - current) / 1000000000.0;

final var executorService = Executors.newSingleThreadExecutor();

try {
    final var latch = new CountDownLatch(1);

    executorService.submit(() -> {
        try {
            System.out.printf("Thread A : sleep start : %f sec.%n", elapsedSec.getAsDouble());
            TimeUnit.SECONDS.sleep(7);
            System.out.printf("Thread A : sleep end   : %f sec.%n", elapsedSec.getAsDouble());
        } finally {
            latch.countDown();
        }
        return null;
    });

    final var ret = latch.await(5, TimeUnit.SECONDS);
    System.out.printf("await ret = %b : %f sec.%n", ret, elapsedSec.getAsDouble());

} finally {
    executorService.shutdown();
}

final var term = executorService.awaitTermination(10, TimeUnit.SECONDS);
System.out.println("term : " + term);

// 結果
// ↓
//Thread A : sleep start : 0.002323 sec.
//await ret = false : 5.009722 sec.
//Thread A : sleep end   : 7.009743 sec.
//term : true

void countDown ()

ラッチのカウントを減算し、カウントがゼロに達すると待機中のスレッドをすべて解放します。

countDown の使用例は、概要 および コンストラクタ のコード例をご参照ください。

long getCount ()

現在のカウントを返します。

getCount の使用例は、概要 のコード例をご参照ください。

String toString ()

ラッチおよびその状態を識別する文字列を返します。

final var latch = new CountDownLatch(2);

final var str1 = latch.toString();
System.out.println(str1); // java.util.concurrent.CountDownLatch@2de56eb2[Count = 2]

latch.countDown();

final var str2 = latch.toString();
System.out.println(str2); // java.util.concurrent.CountDownLatch@2de56eb2[Count = 1]

latch.countDown();

final var str3 = latch.toString();
System.out.println(str3); // java.util.concurrent.CountDownLatch@2de56eb2[Count = 0]

関連記事

ページの先頭へ