アリ側:CyclicBarrierとCountDownLatchの違いは?

前書き

前回の記事「Javaプログラミングの基本的な3つの武器CountDownLatchの高い同時実行性」には欠点があります。つまり、カウンターが1回しか使用できないことです。つまり、別のスレッド呼び出しがある場合に、カウンター(state)が削減された0ときです。 go await()メソッドを使用すると、スレッドは直接渡され、他のスレッドの実行結果が同期するのを待つ必要がなくなります。この問題を解決するためにCyclicBarrier生まれました。

CyclicBarrierとは

CyclicBarrierそれは何ですか?分解してループ(Cycle)とバリア(Barrier)に変換します
ここに画像の説明を挿入します
主な機能はCountDownLanch同じです。バリアに到達したときにスレッドのグループをブロックします。最後のスレッドがバリアに到達するまで、バリアが開きます。バリアによってブロックされたスレッドは引き続き実行されますが、ループで実行できることがCountDownLanch最大の違いです。CountDownLanch最後のスレッドがカウンターを設定した場合にのみ0、他のブロックされたスレッドは実行を継続します。勉強CyclicBarrierする前にこれらの記事を読むことをお勧めします:

  • 「AQSのJava高並行性プログラミング財団」
  • 「Java高並行性プログラミングの基本的な3つの優れたツールセマフォ」
  • 「CountDownLatch、Java High Concurrency ProgrammingFoundationの3つの優れた武器」

    使い方

    まずは最初のを見てみましょうCyclicBarrier1をdemo:ゲーム内のレベルがあった場合たとえば、あなたは次のレベルを入力するたびに、あなたは上のいくつかのマップ、特殊効果の背景音楽などをロードする必要があるゲームが唯一できます。すべてがロードされた後に再生されます:

    /**demo 来源https://blog.csdn.net/lstcui/article/details/107389371
    * 公众号【java金融】
    */
    public class CyclicBarrierExample {
    static class PreTaskThread implements Runnable {
        private String task;
        private CyclicBarrier cyclicBarrier;
    
        public PreTaskThread(String task, CyclicBarrier cyclicBarrier) {
            this.task = task;
            this.cyclicBarrier = cyclicBarrier;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 4; i++) {
                Random random = new Random();
                try {
                    Thread.sleep(random.nextInt(1000));
                    System.out.println(String.format("关卡 %d 的任务 %s 完成", i, task));
                    cyclicBarrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
                System.out.println("本关卡所有的前置任务完成,开始游戏... ...");
            });
            new Thread(new PreTaskThread("加载地图数据", cyclicBarrier)).start();
            new Thread(new PreTaskThread("加载人物模型", cyclicBarrier)).start();
            new Thread(new PreTaskThread("加载背景音乐", cyclicBarrier)).start();
        }
    }
    }

    出力結果は次のとおりです。
    ここに画像の説明を挿入します
    ゲームが開始されるたびに、現在のレベルがゲームのキャラクターモデル、マップデータ、およびバックグラウンドミュージックをロードした後にゲームが開始されることがわかります。そしてそれはまだ周期的に制御することができます。

    ソースコード分析

    構造と構成

    /** The lock for guarding barrier entry */
    private final ReentrantLock lock = new ReentrantLock();
    /** Condition to wait on until tripped */
    private final Condition trip = lock.newCondition();
    /** The number of parties */
    private final int parties;
    /* The command to run when tripped */
    private final Runnable barrierCommand;
    /** The current generation */
    private Generation generation = new Generation();
  • ロック:バリアへの入り口を保護するために使用されるロック
  • トリップ:バリアに到達して手放せないスレッドがトリップ条件変数を待機しています
  • パーティー:フェンスを開くために必要な到着スレッドの総数
  • バリアコマンド:最後のスレッドがバリアに到達した後に実行されるコールバックタスク
  • 生成:これは、CyclicBarrier再利用できる内部クラスです。await最大回数に達するとnew、次のサイクルに入ったことを示すために再作成されます。boolean現在のサイクルでスレッドの中断があるかどうかを示すために使用されるtype属性は1つだけです。

    主な方法

    await方法

    public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
    /**
     * Main barrier code, covering the various policies.
     */
    private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();
         try {
               //获取barrier当前的 “代”也就是当前循环
             final Generation g = generation;
            if (g.broken)
                throw new BrokenBarrierException();
    
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            // 每来一个线程调用await方法都会进行减1
            int index = --count;
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    final Runnable command = barrierCommand;
                    // new CyclicBarrier 传入 的barrierCommand, command.run()这个方法是同步的,如果耗时比较多的话,是否执行的时候需要考虑下是否异步来执行。
                    if (command != null)
                        command.run();
                    ranAction = true;
                    // 这个方法1. 唤醒所有阻塞的线程,2. 重置下count(count 每来一个线程都会进行减1)和generation,以便于下次循环。
                    nextGeneration();
                    return 0;
                } finally {
                    if (!ranAction)
                        breakBarrier();
                }
            }
    
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                     // 进入if条件,说明是不带超时的await
                    if (!timed)
                         // 当前线程会释放掉lock,然后进入到trip条件队列的尾部,然后挂起自己,等待被唤醒。
                        trip.await();
                    else if (nanos > 0L)
                         //说明当前线程调用await方法时 是指定了 超时时间的!
                        nanos = trip.awaitNanos(nanos);
                } catch (InterruptedException ie) {
                     //Node节点在 条件队列内 时 收到中断信号时 会抛出中断异常!
                    //g == generation 成立,说明当前代并没有变化。
                    //! g.broken 当前代如果没有被打破,那么当前线程就去打破,并且抛出异常..
                    if (g == generation && ! g.broken) {
                        breakBarrier();
                        throw ie;
                    } else {
                        // We're about to finish waiting even if we had not
                        // been interrupted, so this interrupt is deemed to
                        // "belong" to subsequent execution.
                    //执行到else有几种情况?
                    //1.代发生了变化,这个时候就不需要抛出中断异常了,因为 代已经更新了,这里唤醒后就走正常逻辑了..只不过设置下 中断标记。
                    //2.代没有发生变化,但是代被打破了,此时也不用返回中断异常,执行到下面的时候会抛出  brokenBarrier异常。也记录下中断标记位。
                        Thread.currentThread().interrupt();
                    }
                }
               //唤醒后,执行到这里,有几种情况?
              //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
              //2.当前Generation被打破,此时也会唤醒所有在trip上挂起的线程
              //3.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (g.broken)
                    throw new BrokenBarrierException();
               //唤醒后,执行到这里,有几种情况?
            //1.正常情况,当前barrier开启了新的一代(trip.signalAll())
            //2.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (g != generation)
                    return index;
               //唤醒后,执行到这里,有几种情况?
            //.当前线程trip中等待超时,然后主动转移到 阻塞队列 然后获取到锁 唤醒。
                if (timed && nanos <= 0L) {
                    breakBarrier();
                    throw new TimeoutException();
                }
            }
        } finally {
             lock.unlock();
        }
    }

    概要

    この時点で、CyclicBarrierループカウントを実行できる理由を知ることができますか?
    CyclicBarrier内部クラスGeneration使用して現在のサイクルを維持し、各awaitメソッドは現在のを格納し、同じグループに属するgeneration同じgenerationオブジェクトを取得します。count空乏化の数がnew1にGenerationなり、のcount値がリセットされるたびpartiesに、新しいを入力することを示します。サイクル。
    このawaitようにして、スレッドが中断されていない限りgenerationbroken意志の現代が設定されtrue、他のスレッドがスローされることにつながることがわかりBrokenBarrierExceptionます。それは失敗に相当し、もう一方も失敗しなければならず、「強い一貫性」のように感じます。

    総括する

  • CountDownLanchカウンタの値を設定します。複数回実行した後countdownカウンタがデクリメントさ0れるすべてのスレッドがウェイクアップされ、その後CountDownLanch無効になり、1回しか使用できません。
  • CyclicBarrierときcountのために0、同じウェイクアップすべてのスレッド、そしてそれがリセットされますcountparties、再再利用を実現します。newgeneration

終わり

  • 私の知識不足のため、必然的に間違いがあります。何かおかしいと思ったら、メッセージを残して指摘してください。訂正させていただきます。
  • 記事が悪くないと思うなら、転送、共有、賞賛、いいね、コメントがあなたの最大の励ましです。
  • 読んでいただきありがとうございます、ようこそ、そしてご清聴ありがとうございました。

巨人の肩の上からリンゴを選ぶ

https://javajr.cn/
http://www.360doc.com/content/20/0812/08/55930996_929792021.shtml
https://www.cnblogs.com/xxyyy/p/12958160.html

おすすめ

転載: blog.51cto.com/14987832/2667207