1. 序章

このチュートリアルでは、CyclicBarrierCountDownLatchを比較し、2つの類似点と相違点を理解しようとします。

2. これは何?

並行性に関しては、それぞれが何を達成しようとしているのかを概念化するのは難しい場合があります。

何よりもまず、CountDownLatchとCyclicBarrierの両方がマルチスレッドアプリケーションの管理に使用されます。

そして、これらは両方とも、特定のスレッドまたはスレッドのグループがどのように待機するかを表現することを目的としています。

2.1.  CountDownLatch

CountDownLatch は、スレッドが待機し、他のスレッドがラッチでゼロに達するまでカウントダウンする構造です。

これは、準備中のレストランの料理のように考えることができます。 どの料理人がnアイテムの多くを準備しても、ウェイターはすべてのアイテムがプレートに載るまで待機する必要があります。 プレートがnアイテムを取る場合、料理人はプレートに置く各アイテムのラッチでカウントダウンします。

2.2.  CyclicBarrier

CyclicBarrier は、すべてのスレッドが到着するまで、スレッドのグループが一緒に待機する再利用可能な構造です。 その時点で、バリアが解除され、オプションでアクションを実行できます。

これは友達のグループのように考えることができます。 彼らがレストランで食事をすることを計画するたびに、彼らは彼らが会うことができる共通のポイントを決定します。 彼らはそこでお互いを待っており、全員が到着したときにのみ、一緒に食事をするためにレストランに行くことができます。

2.3. 参考文献

そして、これらのそれぞれの詳細については、それぞれCountDownLatchCyclicBarrierに関する以前のチュートリアルを参照してください。

3. タスク対。 スレッド

これら2つのクラスのセマンティックの違いのいくつかを詳しく見ていきましょう。

定義に記載されているように、 CyclicBarrier では複数のスレッドが相互に待機できますが、CountDownLatchでは1つ以上のスレッドが複数のタスクの完了を待機できます。

つまり、 CyclicBarrierはスレッドの数を維持し、CountDownLatchはタスクの数を維持します。

次のコードでは、カウントが2のCountDownLatchを定義します。 次に、1つのスレッドから countDown()を2回呼び出します。

CountDownLatch countDownLatch = new CountDownLatch(2);
Thread t = new Thread(() -> {
    countDownLatch.countDown();
    countDownLatch.countDown();
});
t.start();
countDownLatch.await();

assertEquals(0, countDownLatch.getCount());

ラッチがゼロに達すると、awaitへの呼び出しが戻ります。

この場合、同じスレッドでカウントを2回減らすことができたことに注意してください。

CyclicBarrier、は、この点で異なります。

上記の例と同様に、 CyclicBarrier、を再度2カウントで作成し、同じスレッドから await()を呼び出します。

CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Thread t = new Thread(() -> {
    try {
        cyclicBarrier.await();
        cyclicBarrier.await();    
    } catch (InterruptedException | BrokenBarrierException e) {
        // error handling
    }
});
t.start();

assertEquals(1, cyclicBarrier.getNumberWaiting());
assertFalse(cyclicBarrier.isBroken());

ここでの最初の違いは、待機しているスレッド自体がバリアであるということです。

次に、さらに重要なことに、 2番目のawait()は役に立たない1つのスレッドでバリアを2回カウントダウンすることはできません。

実際、 tは別のスレッドがawait()を呼び出すのを待機する必要があるため、カウントを2にするために– tの2番目 await()の呼び出しは、バリアがすでに解除されるまで実際には呼び出されません。

私たちのテストでは、バリアを通過していません。これは、待機しているスレッドが1つだけであり、バリアがトリップするために必要な2つのスレッドがないためです。これはcyclicBarrierからも明らかです。 .isBroken()メソッド。falseを返します。

4. 再利用性

これら2つのクラスの2番目に明らかな違いは、再利用性です。 詳細には、 CyclicBarrierでバリアがトリップすると、カウントが元の値にリセットされます。 カウントがリセットされないため、CountDownLatchは異なります。

与えられたコードでは、カウント7で CountDownLatch を定義し、20の異なる呼び出しでカウントします。

CountDownLatch countDownLatch = new CountDownLatch(7);
ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
    es.execute(() -> {
        long prevValue = countDownLatch.getCount();
        countDownLatch.countDown();
        if (countDownLatch.getCount() != prevValue) {
            outputScraper.add("Count Updated");
        }
    }); 
} 
es.shutdown();

assertTrue(outputScraper.size() <= 7);

20の異なるスレッドがcountDown()を呼び出しても、ゼロに達するとカウントはリセットされないことがわかります。

上記の例と同様に、カウント7の CyclicBarrier を定義し、20の異なるスレッドから待機します。

CyclicBarrier cyclicBarrier = new CyclicBarrier(7);

ExecutorService es = Executors.newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
    es.execute(() -> {
        try {
            if (cyclicBarrier.getNumberWaiting() <= 0) {
                outputScraper.add("Count Updated");
            }
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            // error handling
        }
    });
}
es.shutdown();

assertTrue(outputScraper.size() > 7);

この場合、値がゼロに達すると元の値にリセットすることにより、新しいスレッドが実行されるたびに値が減少することがわかります。

5. 結論

全体として、CyclicBarrierCountDownLatchはどちらも、複数のスレッド間の同期に役立つツールです。 ただし、提供する機能の点では根本的に異なります。 どちらが仕事に適しているかを判断するときは、それぞれを慎重に検討してください。

いつものように、説明したすべての例は、Githubからにアクセスできます。