Javaの並行処理ユーティリティ:CountDownLatchとCyclicBarrierの使い方

CountDownLatchの概要

CountDownLatchは、スレッド間の同期を支援するためのユーティリティクラスです。このクラスは、一つまたは複数のスレッドが、他のスレッドが特定の操作を完了するのを待つことができるように設計されています。名前の通り、カウントダウン式のロックであり、指定された回数の「カウントダウン」が完了するまで、待機するスレッドをブロックします。

CountDownLatchの基本的な使用例

以下の例では、メインスレッドが、3つのワーカースレッドがそれぞれのタスクを完了するのを待ちます。

public class LatchExample {
    public static void main(String[] args) throws InterruptedException {
        // CountDownLatchのインスタンスを作成(カウント数を3に設定)
        CountDownLatch latch = new CountDownLatch(3);
        System.out.println("メインスレッドが3つのタスクの完了を待っています...");

        // タスク1を開始するスレッド
        new Thread(() -> {
            System.out.println("タスク1を開始します...");
            try {
                Thread.sleep(1500); // 1.5秒間処理をシミュレート
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("タスク1が完了しました。");
            latch.countDown(); // カウントを1減らす
        }).start();

        // タスク2を開始するスレッド
        new Thread(() -> {
            System.out.println("タスク2を開始します...");
            try {
                Thread.sleep(1000); // 1秒間処理をシミュレート
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("タスク2が完了しました。");
            latch.countDown(); // カウントを1減らす
        }).start();

        // タスク3を開始するスレッド
        new Thread(() -> {
            System.out.println("タスク3を開始します...");
            try {
                Thread.sleep(2000); // 2秒間処理をシミュレート
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("タスク3が完了しました。");
            latch.countDown(); // カウントを1減らす
        }).start();

        // メインスレッドが、カウントが0になるまで待機
        latch.await();
        System.out.println("すべてのタスクが完了しました。メインスレッドが続行します。");
    }
}

スレッドプールとの組み合わせ

CountDownLatchは、スレッドプール(ExecutorService)と組み合わせて使用することも一般的です。以下の例では、スレッドプール内のタスクが完了するのを待ちます。

public class LatchWithPool {
    public static void main(String[] args) {
        // CountDownLatchのインスタンスを作成
        CountDownLatch latch = new CountDownLatch(3);
        // 固定サイズのスレッドプールを作成
        ExecutorService executor = Executors.newFixedThreadPool(4);

        // メインタスク:他のタスクの完了を待つ
        executor.submit(() -> {
            System.out.println("メインタスクが他のタスクの完了を待っています...");
            try {
                latch.await(); // 待機
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("すべてのタスクが完了しました。メインタスクが後続の処理を開始します。");
            try {
                Thread.sleep(1000); // 後続処理のシミュレート
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("メインタスクの処理も終了しました。");
        });

        // ワーカータスク1
        executor.submit(() -> {
            System.out.println("ワーカータスク1を開始します...");
            try {
                Thread.sleep(800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ワーカータスク1が完了しました。");
            latch.countDown();
        });

        // ワーカータスク2
        executor.submit(() -> {
            System.out.println("ワーカータスク2を開始します...");
            try {
                Thread.sleep(1200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ワーカータスク2が完了しました。");
            latch.countDown();
        });

        // ワーカータスク3
        executor.submit(() -> {
            System.out.println("ワーカータスク3を開始します...");
            try {
                Thread.sleep(1800);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ワーカータスク3が完了しました。");
            latch.countDown();
        });

        // スレッドプールをシャットダウン
        executor.shutdown();
    }
}

CountDownLatchによるゲームローディングのシミュレーション

以下の例では、複数のプレイヤーがゲームにログインするのを待つシナリオをシミュレートします。

public class GameLoader {
    public static void main(String[] args) throws InterruptedException {
        // プレイヤー数に基づいてCountDownLatchを作成
        int playerCount = 5;
        CountDownLatch latch = new CountDownLatch(playerCount);
        // プレイヤーの進捗を追跡する配列
        String[] progress = new String[playerCount];
        // キャッシュスレッドプールを使用してプレイヤーとゲームをシミュレート
        ExecutorService service = Executors.newCachedThreadPool();

        // ゲームのロードを管理するメインスレッド
        service.submit(() -> {
            try {
                Thread.sleep(4000); // ゲームの初期化をシミュレート
                latch.await(); // すべてのプレイヤーがロード完了するのを待つ
                System.out.println("
すべてのプレイヤーがロードされました。戦闘開始準備完了!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // プレイヤーを生成
        for (int i = 0; i < playerCount; i++) {
            final int playerId = i + 1;
            service.submit(() -> {
                for (int j = 0; j <= 100; j += 10) {
                    Thread.sleep(200); // ロード時間のシミュレート
                    progress[playerId - 1] = "プレイヤー" + playerId + ": " + j + "%";
                    // 進捗をコンソールに表示
                    System.out.print("\r" + Arrays.toString(progress));
                }
                latch.countDown(); // プレイヤーのロード完了を通知
            });
        }

        service.shutdown();
    }
}

CyclicBarrierの概要

CyclicBarrierは、別の種類の並行処理制御ツールです。CountDownLatchと非常に似ていますが、より複雑で強力な機能を持ちます。CyclicBarrierは「循環バリア」と理解できます。CountDownLatchとの主な違いは、一度バリアが解除されると、再度使用できる「循環的」な機能を持っている点です。つまり、指定された数のスレッドがバリアに到達すると、すべてのスレッドが解放され、バリアはリセットされて再利用できます。

タグ: Java 並行処理 CountDownLatch CyclicBarrier JUC

7月2日 21:13 投稿