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

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


概要

クラス構成

CountDownLatchは、スレッドの処理をブロック/リセットするために使います。

大まかな流れは…

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

となります。

シーケンス図

コード例です。

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

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

final ExecutorService executorService = Executors.newFixedThreadPool(2);

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

    executorService.submit(() -> {
        try {
            System.out.println("サブスレッドA : sleep 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            Thread.sleep(1000); // 1秒待機
            System.out.println("サブスレッドA : sleep 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        } finally {
            latch.countDown();
            System.out.println("サブスレッドA : latch count : " + latch.getCount());
        }
        return null;
    });

    executorService.submit(() -> {
        try {
            System.out.println("サブスレッドB : sleep 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            Thread.sleep(2000); // 2秒待機
            System.out.println("サブスレッドB : sleep 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        } finally {
            latch.countDown();
            System.out.println("サブスレッドB : latch count : " + latch.getCount());
        }
        return null;
    });

    System.out.println("latch.await 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
    latch.await();
    System.out.println("latch.await 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));

} finally {
    executorService.shutdown();
}

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

// 結果
// ↓
//latch count : 2
//latch.await 開始 : 0.005203 sec.
//サブスレッドA : sleep 開始 : 0.005032 sec.
//サブスレッドB : sleep 開始 : 0.005337 sec.
//サブスレッドA : sleep 終了 : 1.015773 sec.
//サブスレッドA : latch count : 1
//サブスレッドB : sleep 終了 : 2.011131 sec.
//サブスレッドB : latch count : 0
//latch.await 終了 : 2.011956 sec.
//result : true

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

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

コンストラクタ

CountDownLatch (int count)

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

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

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

final ExecutorService executorService = Executors.newSingleThreadExecutor();

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

    executorService.submit(() -> {
        try {
            System.out.println("sleep開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            Thread.sleep(1000);
            System.out.println("sleep終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        } finally {
            latch.countDown();
        }
        return null;
    });

    System.out.println("await開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
    latch.await();
    System.out.println("await終了 : %f sec.".formatted(elapsedSec.getAsDouble()));

} finally {
    executorService.shutdown();
}

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

// 結果
// ↓
//await開始 : 0.002081 sec.
//sleep開始 : 0.002436 sec.
//sleep終了 : 1.006969 sec.
//await終了 : 1.007525 sec.
//result : true

メソッド

void await ()

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

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

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

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

final ExecutorService executorService = Executors.newFixedThreadPool(2);

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

    final var future = executorService.submit(() -> {
        try {
            System.out.println("スレッドA 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            latch.await();

        } catch (InterruptedException e) {
            System.out.println("InterruptedException発生!");
        } finally {
            System.out.println("スレッドA 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        }
    });

    executorService.submit(() -> {
        System.out.println("sleep開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
        Thread.sleep(5000);
        System.out.println("sleep終了 : %f sec.".formatted(elapsedSec.getAsDouble()));

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

        return null;
    });

} finally {
    executorService.shutdown();
}

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

// 結果
// ↓
//スレッドA 開始 : 0.002385 sec.
//sleep開始 : 0.002607 sec.
//sleep終了 : 5.016575 sec.
//InterruptedException発生!
//スレッドA 終了 : 5.017503 sec.
//cancel : true
//result : true

boolean await (long timeout, TimeUnit unit)

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

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

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

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

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

final ExecutorService executorService = Executors.newFixedThreadPool(2);

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

    executorService.submit(() -> {
        try {
            System.out.println("サブスレッドA : sleep 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            Thread.sleep(2000);
            System.out.println("サブスレッドA : sleep 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        } finally {
            latch.countDown();
        }
        return null;
    });

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

} finally {
    executorService.shutdown();
}

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

// 結果
// ↓
//サブスレッドA : sleep 開始 : 0.003518 sec.
//サブスレッドA : sleep 終了 : 2.012785 sec.
//await ret = true : 2.013733 sec.
//result : true

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

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

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

final ExecutorService executorService = Executors.newFixedThreadPool(2);

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

    executorService.submit(() -> {
        try {
            System.out.println("サブスレッドA : sleep 開始 : %f sec.".formatted(elapsedSec.getAsDouble()));
            Thread.sleep(7000);
            System.out.println("サブスレッドA : sleep 終了 : %f sec.".formatted(elapsedSec.getAsDouble()));
        } finally {
            latch.countDown();
        }
        return null;
    });

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

} finally {
    executorService.shutdown();
}

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

// 結果
// ↓
//サブスレッドA : sleep 開始 : 0.002060 sec.
//await ret = false : 5.014917 sec.
//サブスレッドA : sleep 終了 : 7.009526 sec.
//result : true

void countDown ()

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

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

long getCount ()

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

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

String toString ()

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

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

latch.countDown();
System.out.println(latch.toString()); // java.util.concurrent.CountDownLatch@254c873[Count = 1]

関連記事

ページの先頭へ