CyclicBarrier マルチスレッド プログラミングのソース コード解析

CyclicBarrier ソースコード分析 CountDownLatch を強制終了する方法

循環障壁とは

CyclicBarrier一般にバリアとして知られている栅栏、マルチスレッド同期ツールです。

CountDownLatchどちらもスレッドのグループを待機するためのツールであるため、と比較されることがよくあります。

CountDownLatchワーカー スレッドのグループを待機し、ワーカー スレッドが到着した後に通知を行い、実行を継続するためによく使用される調整スレッドとは異なり、CyclicBarrierその名前が示すように、スレッドのグループは、指定された場所に到着するのを相互に待機します。実行を続ける前に。

CyclicBarrierより高度なものは次のとおりです。

  • 再利用可、実行後または手動リセット後に再利用可能、CountDownLatch直接廃棄。

  • コールバック ロジックの実行が許可されます。通常は、フェンス呼び出し自体に到達する最後のスレッドです。この機能を使用して、ビジネス ロジックの最後の仕上げを行うことができます。

  • 問題が発生した場合は、現在の世代を破棄 (他のスレッドに通知) し、次回のためにリセットすることができます。決められた条件にCountDownLatch到達した、CyclicBarrier垣根を越えて待たなければなりません。ワーカー スレッドが到着する前に例外を取得した場合は、手動でリセットする必要があります。

    もちろん、異常やタイムアウトなどで現在の使用は自動的に破棄されますが、そのまま次回に使用することはできず、手動でリセットする必要があることに注意してください。

サイクリックバリアの使い方

CountDownLatch: 複数のロックを備えた金庫 (juejin.cn)キューはCyclicBarrier、安全ロックを開いた後、使用できなくなることを示しています。

画像-20210621163638007

再利用を許可する

もちろん、仲間たちは、上司がロックを解除してお金を取り、逃げた後、上司がチェックに戻ったときに気付かないことを望んでいます。画像-20210621164945886

private static void normal() throws InterruptedException {
    
    
    CyclicBarrier barrier = new CyclicBarrier(3);
    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
    }
    System.out.println("亲信们:不成功便成仁!!!!!");
    for (int i = 0; i < 3; i++) {
    
    
        Thread thread = new Thread(new MyFollower(barrier));
        thread.start();
    }
    Thread.sleep(1000);
    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
        System.out.println("老板:很好,这东西不错");
    }
}

private static class MyFollower implements Runnable {
    
    
    private CyclicBarrier barrier;

    public MyFollower(CyclicBarrier barrier) {
    
    
        this.barrier = barrier;
    }

    @Override
    public void run() {
    
    
        System.out.println("亲信:输入密码ing");
        try {
    
    
            barrier.await();
        } catch (InterruptedException e) {
    
    
            System.out.println("临死前:骂骂咧咧地退出了游戏");
        } catch (BrokenBarrierException e) {
    
    
            System.out.println("OS:傻子,这都输入错了");
        }
        System.out.println("亲信:成了!!!!!");
    }
}
保险箱:安全保护中
    
亲信们:不成功便成仁!!!!!
    
亲信:输入密码ing
亲信:输入密码ing
亲信:输入密码ing
    
亲信:成了!!!!!
亲信:成了!!!!!
亲信:成了!!!!!
    
保险箱:安全保护中
老板:很好,这东西不错
信頼できる友人が殺されたり (中断)、間違った入力 (自発的な破壊)、または遅い入力 (タイ​​ムアウト) の場合、私の安全ロックにとって何が問題になるのでしょうか?

内部スレッドの割り込み、またはアクティブな破棄はブール値をチェックするため、この場合、他のスレッドは何が破棄を引き起こしたのかを知ることができません。

画像-20210621174527946

シンプルなコードの実装 (実際の状況では、より完璧な調整と処理が必要です)

private static void normal() throws InterruptedException {
    
    
    CyclicBarrier barrier = new CyclicBarrier(3);
    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
    }
    System.out.println("亲信们:不成功便成仁!!!!!");
    for (int i = 0; i < 3; i++) {
    
    
        Thread thread = new Thread(new MyFollower(barrier, i + 1));
        thread.start();
        if (i == 0) {
    
    
            thread.interrupt();
        }
    }
    Thread.sleep(2000);
    if (barrier.isBroken()) {
    
    
        System.out.println("亲信:重置一下");
        barrier.reset();
    }

    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
        System.out.println("老板:很好,这东西不错");
    }
}

private static class MyFollower implements Runnable {
    
    
    private CyclicBarrier barrier;
    private int no;

    public MyFollower(CyclicBarrier barrier, int no) {
    
    
        this.barrier = barrier;
        this.no = no;
    }

    @Override
    public void run() {
    
    
        System.out.println("亲信:输入密码ing");
        if (no == 3) {
    
    
            System.out.println("亲信:输错了...重置一下");
            barrier.reset();
            return;
        }
        if (no == 2) {
    
    
            System.out.println("亲信:输慢了...");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            return;
        }
        try {
    
    
            barrier.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
    
    
            System.out.println("临死前:骂骂咧咧地退出了游戏");
            return;
        } catch (BrokenBarrierException e) {
    
    
            System.out.println("OS:哪个傻子错了啊");
            return;
        } catch (TimeoutException e) {
    
    
            System.out.println("亲信:是谁输入慢了");
            return;
        }
        System.out.println("亲信:成了!!!!!");
    }
メーカー独自のロック解除結果(コールバック関数)

安全ロックのメーカーは、消費者がロック解除後にお祝い、通知などをカスタマイズできるようにします。どうやってするの?

画像-20210621175156339

private static void normal() throws InterruptedException {
    
    
    CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    
    
        System.out.println("保险箱:恭喜主人,又拿到钱了!!!");
    });
    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
    }
    System.out.println("亲信们:不成功便成仁!!!!!");
    for (int i = 0; i < 3; i++) {
    
    
        Thread thread = new Thread(new MyFollower(barrier));
        thread.start();
    }
    Thread.sleep(1000);
    if (!barrier.isBroken()) {
    
    
        System.out.println("保险箱:安全保护中");
        System.out.println("老板:很好,这东西不错");
    }
}
保险箱:安全保护中
    
亲信们:不成功便成仁!!!!!
    
亲信:输入密码ing
亲信:输入密码ing
亲信:输入密码ing
    
亲信:成了!!!!!
亲信:成了!!!!!
亲信:成了!!!!!
保险箱:恭喜主人,又拿到钱了!!!
    
保险箱:安全保护中
老板:很好,这东西不错

注: コールバック関数は、使用方法に応じて何でも実行できます。しかし、これは最後にフェンスに到達したワーカー スレッドによって実行されます。

CyclicBarrier のソース コード

CyclicBarrierAQSシステムで考えてみましょうが、AQSを直接継承してCountDownLatch内部、同じ内部拡張AQSを使用しますReentrantLock

AQS について、以下を表示ReenrtantLockできます。

これに基づいて、CyclicBarrierの。

  • 複数のスレッドの並行性を確保するために、ロックが必要です
  • 条件付き、フェンスでの集団待機用
  • すべてが到着したかどうかを計算するカウンターが必要です
  • 参加者数を取得するには、カウンターをリセットするために使用します
  • 壊れた状態を持っている必要があります
  • リセットされたカウンターと状態が前のものに影響を与えないようにするための世代別対策が必要です。
  • コールバック関数を保存します。
プロパティと内部クラス
private static class Generation {
    
    
    Generation() {
    
    }                 // prevent access constructor creation
    // 标识栅栏破坏,线程不会再等待
    // 一般几种情况:
    // 1.等待超时
    // 2.线程中断
    // 3.手动重置
    // 4.回调执行异常
    boolean broken;                 // initially false
}
// 用于控制对状态、数量操作的并发控制
private final ReentrantLock lock = new ReentrantLock();
// 线程等待在栅栏处的条件变量
private final Condition trip = lock.newCondition();
// 参与线程的数量
private final int parties;
// 完成时的回调动作,由最后一个到达栅栏的线程执行
private final Runnable barrierCommand;
// 允许重用下的分代
private Generation generation = new Generation();

// 剩下还没达到等待栅栏的线程数量, 每次到达就 --
// 初始值为 parties;
// parties - count = 正在等待的线程数量
private int count;

比較推測:

  • ReentrantLock lock複数のスレッドの同時実行性を確保するために、ロックが必要です
  • 条件付きCondition trip、フェンスでの集団待機用
  • countすべてが到着したかどうかを計算するカウンターが必要です
  • 参加者の数を取得するにはparties、カウンターをリセットするために使用します
  • 壊れた状態を持っている必要がありますgeneration.broken
  • generationリセット カウンターが前のカウンターに影響を与えないようにするには、世代別の対策が必要です。
  • コールバック関数を保存しますbarrierCommand

ただしGeneration、の世代処理は比較的単純で、前の世代の他の状態は保持されず、直接リセットされます。破損した状態のみが保持され、まだ実行プロセスにあるスレッドの終了を制限するために使用されます。前の世代の。

したがって、リセットおよび通知メソッドは次のとおりです。

注: 混乱を避けるために、通常、これらのメソッドへの内部呼び出しはロックのみを要求する必要があります

次世代へnextGeneration

無事に次の世代に入ることができたということは、全員が無事にバリアに到達し、コールバック関数が正常に実行されたことを意味します。

// 进入下一个分代
private void nextGeneration() {
    
    
    // signal completion of last generation
    // 唤醒上一个分代中的等待线程
    trip.signalAll();
    // 重置 count 计数
    count = parties;
    generation = new Generation();
}
旗荒らし

一般的な状況は、中断、タイムアウト、実行失敗などです。

private void breakBarrier() {
    
    
    generation.broken = true;
    count = parties;
    // 唤醒当前分代中的等待线程
    trip.signalAll();
}

スレッドに通知する 2 つの操作は、 のリセットcountを。違いは、generationと がそれぞれ 2 回操作されることですbroken

スレッドがフェンスを待つとき、状況に応じて 2 つの値を判断することを説明します。

コンストラクタ
public CyclicBarrier(int parties, Runnable barrierAction) {
    
    
    if (parties <= 0) throw new IllegalArgumentException();
    this.parties = parties;
    this.count = parties;
    // 指定回调动作
    this.barrierCommand = barrierAction;
}

public CyclicBarrier(int parties) {
    
    
    this(parties, null);
}
コアメソッド
待つ
public int await() throws InterruptedException, BrokenBarrierException {
    
    
    try {
    
    
        return dowait(false, 0L);
    } catch (TimeoutException toe) {
    
    
        // 不响应超时,所以需要处理一下超时情况
        throw new Error(toe); // cannot happen
    }
}
public int await(long timeout, TimeUnit unit)
    throws InterruptedException, BrokenBarrierException, TimeoutException {
    
    
    return dowait(true, unit.toNanos(timeout));
}

待機メソッドが内部的に呼び出されていることがわかりますdowaitメソッドの例外リストから判断すると、中断、破棄、およびタイムアウトの例外を実行できる大規模で包括的な処理メソッドです。

待って
/**
* @param timed 是否允许超时
* @param nanos 超时时间, 只有 timed = true  才有意义
* @return 线程到达栅栏的索引: 第一个:parties - 1; 最后一个:0
*/
private int dowait(boolean timed, long nanos)
    throws InterruptedException, BrokenBarrierException,
           TimeoutException {
    
    
    final ReentrantLock lock = this.lock;
    // 1.核心逻辑得加锁
    lock.lock();
    try {
    
    
        final Generation g = generation;

        if (g.broken)
            // 2.如果已经破坏, 必须手动重置才能使用
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
    
    
            // 3.提前检查当前执行线程已经中断了, 那收尾:破坏栅栏,顺便唤醒所有等待线程
            breakBarrier();
            throw new InterruptedException();
        }

        int index = --count;
        if (index == 0) {
    
      // 4.全部到达了,当前线程是最后一个
            boolean ranAction = false;
            try {
    
    // 最后一个执行线程负责执行回调
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                ranAction = true;
                // 全部到达,执行成功:进入下一代
                nextGeneration();
                return 0;
            } finally {
    
    
                if (!ranAction)
                    // 执行失败也是做收尾
                    breakBarrier();
            }
        }

        // loop until tripped, broken, interrupted, or timed out
        // 5.循环等待,直到中断、破坏、超时的任意情况发生
        for (;;) {
    
    
            try {
    
    
                // 判断是超时或不超时等待
                if (!timed)
                    trip.await();
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
    
    
                // 6.判断是外部还是内部中断
                if (g == generation && ! g.broken) {
    
    
                    // 6.1说明是外部其他线程中断的
                    // 进行收尾的同时, 得抛出异常
                    breakBarrier();
                    throw ie;
                } else {
    
    
                    // 6.2如果进行到这里,那肯定是属于栅栏被破坏,或者全部到达栅栏
                    // 直接恢复中断状态,不需要处理异常
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                // 7。检查是不是属于被破坏唤醒
                throw new BrokenBarrierException();

            if (g != generation)
                // 8。是否结束了, 相当于返回序号
                return index;

            // 9.没结束,判断是否超时:超时收尾
            if (timed && nanos <= 0L) {
    
    
                breakBarrier();
                throw new TimeoutException();
            }
            // 我也不清除啥情况会继续循环.
            // 如果会循环, 那中断状态会被延续到下一轮的第3步检查中断,从而抛出中断异常
        }
    } finally {
    
    
        lock.unlock();
    }
}

最初のステップは最初にロックを取得することです。これは、カウンター操作であろうと、nextGenerationブロックbreakBarrier状態に入ろうと、ロックによって保証されているためです。

手順 6 では、外部割り込みか内部割り込みかに関係なく、状況によって待機中に割り込みが発生する場合がありますnextGenerationbreakBarrier

6.1 外部割り込みの場合は、フェンスを破棄して例外をスローします。

6.2 内部中断の場合、中断状態のみを復元し、次のステップに進みます。一般的な割り込みは待機の最後に属するため、例外はスローされません。

6.2 タイムアウト、破棄、次の世代(終了またはリセット)のその後の判定

リセット

Reset は現在の世代の待機を中断するため、 と呼ばれますbreakBarrier

次の世代に入ります: nextGeneration.

リセットが次の世代のみの場合は、後者を直接呼び出すことができますが、現在の世代の待機を中断する必要があります。

したがって、合計で2つの異なる操作のみを組み合わせますbreakBarriernextGeneration

  • generation.broken = true;
  • generation = new Generation();
public void reset() {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        // 唤醒等待线程, 标记已破坏
        breakBarrier();   // break the current generation
        // 进入下次分代
        nextGeneration(); // start a new generation
    } finally {
    
    
        lock.unlock();
    }
}
統計的方法

主な目的は、破損しているかどうかを判断し、待機スレッド数を取得することです。

合わせてgetParties、残りの未到達スレッド数も知ることができます。

public boolean isBroken() {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        return generation.broken;
    } finally {
    
    
        lock.unlock();
    }
}
public int getNumberWaiting() {
    
    
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        return parties - count;
    } finally {
    
    
        lock.unlock();
    }
}
public int getParties() {
    
    
    return parties;
}

メソッドを呼び出すスレッドの数がawait初期設定とparties一致しないことに注意してください

  1. 未満parties、0 にcount減らすこと。最終的にすべての実行awaitスレッドがブロックされます。
  2. 以上parties、いよいよparties到着したばかりで、次の世代に入りました。次の世代では、スレッドの数がpartiesその数よりも多くなり、スレッドのこの部分がブロックされます。

スレッドの数はスレッドの数と等しくなければならずparties、指定されたタイムアウト時間でawaitメソッドお勧めします。

要約する

CountDownLatchに関する例はCyclicBarrierあまり良くありませんが、ほとんど意味を伝えることができません。

要約すると、CyclicBarrierフアンの死の 3 つの主要な犯罪CountDownLatch:

  • 再利用可、実行後または手動リセット後に再利用可能、CountDownLatch直接廃棄。

  • コールバック ロジックの実行が許可されます。通常は、フェンス呼び出し自体に到達する最後のスレッドです。この機能を使用して、ビジネス ロジックの最後の仕上げを行うことができます。

  • 問題が発生した場合は、現在の世代を破棄 (他のスレッドに通知) し、次回のためにリセットすることができます。決められた条件にCountDownLatch到達した、CyclicBarrier垣根を越えて待たなければなりません。ワーカー スレッドが到着する前に例外を取得した場合は、手動でリセットする必要があります。

    もちろん、異常やタイムアウトなどで現在の使用は自動的に破棄されますが、そのまま次回に使用することはできず、手動でリセットする必要があることに注意してください。

おすすめ

転載: blog.csdn.net/jiangxiayouyu/article/details/118108001