Javaの同時実行性:カウントダウンラッチと循環バリア


160

私はを通読しているjava.util.concurrentのAPI、およびことがわかりました

  • CountDownLatch:1つ以上のスレッドが、他のスレッドで実行されている一連の操作が完了するまで待機することを可能にする同期補助。
  • CyclicBarrier:一連のスレッドが互いに共通のバリアポイントに到達するのをすべて待機できるようにする同期補助。

私にはどちらも平等に見えますが、それだけではないでしょう。

たとえば、CoundownLatch, the countdown value could not be reset, that can happen in the case of CyclicBarrier

2つの間に他の違いはありますか?誰かがカウントダウンの値をリセットしたいところ
use casesどこですか?


12
ラッチはイベントを待機するためのものです。バリアは他のスレッドを待つためのものです。-Java Concurrency in Practice、B.Goetz et al。
user2418306 2016

回答:


137

大きな違いの1つは、CyclicBarrierが、共通のバリア条件が満たされたときに実行される(オプションの)Runnableタスクを取ることです。

また、バリアで待機しているクライアントの数とバリアをトリガーするために必要な数を取得することもできます。トリガーされると、バリアはリセットされ、再び使用できます。

単純な使用例-サービスの開始など... CountdownLatchは問題ありません。CyclicBarrierは、より複雑な調整タスクに役立ちます。このようなことの例は、並列計算(複数のサブタスクが計算に関与する場合)のようなもので、MapReduceのようなものです。


6
「バリアで待機しているクライアントの数とバリアをトリガーするために必要な数を取得することもできます。トリガーされると、バリアはリセットされ、再び使用できます。」私はこの点が本当に好きです。私が読んだいくつかの記事では、reset()メソッドを呼び出したため、CyclicBarrierが循環的であることが示唆されています。それは事実ですが、彼らがしばしば言及しないことは、バリアがトリガーされるとすぐにバリアが自動的にリセットされるということです。これを説明するためにいくつかのサンプルコードを投稿します。
Kevin Lee

@Kevin Lee「バリアがトリガーされるとすぐにバリアは自動的にリセットされます。」そのため、コードでreset()を呼び出す必要はありません。
超新星

134

別の違いがあります。

を使用する場合CyclicBarrier、バリアをトリガーする待機スレッドの数を指定することが前提です。5を指定する場合、呼び出すには少なくとも5つのスレッドが必要await()です。

を使用する場合はCountDownLatchcountDown()待機中のすべてのスレッドが解放される呼び出しの数を指定します。これはCountDownLatch、単一のスレッドのみでを使用できることを意味します。

「どうしてそんなことをするの?」と言うかもしれません。コールバックを実行する他の誰かによってコード化された神秘的なAPIを使用していると想像してください。特定のコールバックが何度も呼び出されるまで、スレッドの1つが待機するようにします。コールバックが呼び出されるスレッドがわかりません。この場合、a CountDownLatchは完璧ですが、a を使用してこれを実装する方法は考えられませんCyclicBarrier(実際にはできますが、タイムアウトが発生します...残念です)。

それCountDownLatchがリセットされることを願っています!


10
これは理論的な違いをよりよく示す答えだと思います。バリアが待機するスレッドの正確な量を必要とする一方で、メソッドを複数回呼び出すだけでラッチを解除できるという事実。
flagg19 2013

43
右-それが大きな違いです。CountDownLatch-> NumberOfCalls、CyclicBarrier-> NumberOfThreads
Ivan Voroshilin

1
私はそれがCountDownLatchリセット可能であることは素晴らしいことだと私は同意します-大まかな待機通知を実装するために使用する回避策はCountDownLatch、保護されたコードブロックが入力されたとき(ラッチがゼロに達したとき)にすぐに更新することです。もちろん、これはすべての状況やスコープに当てはまるわけではありませんが、Goldilocksの状況でのオプションであることは注目に値します。
Ephemera

2
このトピックに関する最良の回答の1つ。Java Concurrency in Practice-同じことを言う:Latches are for waiting for events; barriers are for waiting for other threads.。これら2つの違いを理解するための重要なポイント。
Rahul Dev Mishra 2017年

Java 8のドキュメントには、「Nに初期化されたCountDownLatchを使用して、Nスレッドが何らかのアクションを完了するか、一部のアクションがN回完了するまで、1つのスレッドを待機させることができます。」私には思える:CountDownLatch-> NumberOfCallsまたはCountDownLatch-> NumberOfThreads
nir

41

まだ誰も言及していない点の1つは、で、CyclicBarrierスレッドに問題(タイムアウト、中断など)が発生した場合、到達した他のすべてがawait()例外を受け取ることです。Javadocを参照してください。

CyclicBarrierは、失敗した同期の試行に対してオールオアナッシングの破壊モデルを使用します。スレッドが中断、障害、またはタイムアウトのために途中でバリアポイントを離れると、そのバリアポイントで待機している他のすべてのスレッドもBrokenBarrierException(またはInterruptedExceptionそれらもほぼ同時に中断された場合)。


22

JavaDocは違いを明確に説明していると思います。ほとんどの人はCountDownLatchをリセットできないことを知っていますが、CyclicBarrierはリセットできます。ただし、これが唯一の違いではありません。または、CyclicBarrierの名前をResetbleCountDownLatchに変更できます。JavaDocに記述されている目標の観点から違いを伝える必要があります

CountDownLatch: 1つ以上のスレッドが、他のスレッドで実行されている一連の操作が完了するまで待機できるようにする同期補助。

CyclicBarrier:一連のスレッドがお互いが共通のバリアポイントに到達するのをすべて待機できるようにする同期補助。

countDownLatchには、他のスレッドのセットが完了するのを待っている1つ以上のスレッドがあります。この状況では、2種類のスレッドがあります。1つは待機中、もう1つは何かを実行中、タスクが終了した後、待機中または単に終了した可能性があります。

CyclicBarrierでは、スレッドのタイプは1つだけであり、それらは互いに待機しており、同等です。


1
「CyclicBarrierでは、スレッドのタイプは1つだけです」...他のスレッドが.await()を呼び出すまで、それらは「待機の役割」は同じですが、「何をするかは等しくない」場合があります。また、それらはすべて、同じタイプまたは異なるタイプの完全に異なるスレッドインスタンス(!)である必要があります。一方、CountDownLatchでは、同じスレッドがcountDown()を呼び出し、結果に影響を与える可能性があります。
ウラジミールナボコフ

CountDownLatchには本質的に2つの役割が必要であることに同意します。1つはcountDown用、もう1つは待機用です。一方、CyclicBarrierクライアントは、awaitメソッドで問題なく動作します。
isaolmez

14

主な違いは、CountdownLatchのJavadocに記載されています。つまり:

CountDownLatchは、指定されたカウントで初期化されます。countDown()メソッドの呼び出しにより現在のカウントがゼロに達するまで、awaitメソッドはブロックします。その後、待機中のすべてのスレッドが解放され、後続のawaitの呼び出しはすぐに戻ります。これはワンショット現象です。カウントをリセットできません。カウントをリセットするバージョンが必要な場合は、CyclicBarrierの使用を検討してください。

ソース1.6 Javadoc


4
それらの違いがリセットできるかどうかだけの場合、CyclicBarrierの名前はResetableCountDownLatchの方が適している可能性があります。これは、違いにより意味があります。
James.Xu

12

CountDownLatchは、1回限りの同期に使用されます。CountDownLatchを使用している間、任意のスレッドでcountDown()を何度でも呼び出すことができます。他のブロックされていないスレッドによるcountDown()の呼び出しにより、await()を呼び出したスレッドは、カウントがゼロに達するまでブロックされます。CountDownLatchjavadocは次のように述べています。

countDown()メソッドの呼び出しにより現在のカウントがゼロに達するまで、awaitメソッドはブロックします。その後、待機中のすべてのスレッドが解放され、後続のawaitの呼び出しはすぐに戻ります。...

別の典型的な使用法は、問題をNの部分に分割し、その部分を実行してラッチをカウントダウンするRunnableで各部分を記述し、すべてのRunnableをエグゼキューターにキューイングすることです。すべてのサブパートが完了すると、調整スレッドは待機を通過できます。(スレッドがこのように繰り返しカウントダウンする必要がある場合は、代わりにCyclicBarrierを使用してください。)

対照的に、循環バリアは複数の同期ポイントで使用されます。たとえば、一連のスレッドがループ/フェーズ計算を実行していて、次の反復/フェーズを開始する前に同期する必要がある場合などです。CyclicBarrierjavadocによると

バリアは、待機中のスレッドが解放された後に再利用できるため、循環型と呼ばれます。

CountDownLatchとは異なり、await()への各呼び出しは特定のフェーズに属し、そのフェーズに属するすべてのパーティがawait()を呼び出すまでスレッドをブロックする可能性があります。CyclicBarrierがサポートする明示的なcountDown()操作はありません。


12

この質問はすでに適切に回答されていますが、コードを投稿することで少し付加価値を付けることができると思います。

循環バリアの動作を説明するために、サンプルコードをいくつか作成しました。バリアが傾くとすぐに、バリアは自動的にリセットされ、再び使用できるようになります(したがって、「周期的」です)。プログラムを実行するとき、バリアが傾けられた後にのみ、「Let's play」の出力がトリガーされることを確認してください。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierCycles {

    static CyclicBarrier barrier;

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3); 

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

}


class Worker extends Thread {
    @Override
    public void run() {
        try {
            CyclicBarrierCycles.barrier.await();
            System.out.println("Let's play.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}

8

ラッチとサイクリックバリアについて研究していたとき、この比喩を思いつきました。 循環障壁:会社に会議室があると想像してください。会議を開始するには、会議の参加者の一定数が会議に参加する必要があります(正式にするため)。以下は、通常の会議出席者(従業員)のコードです。

class MeetingAtendee implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendee(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + " i joined the meeting ...");
        myMeetingQuorumBarrier.await();
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("Meeting canceled! every body dance <by chic band!>");
    }
 }
}

従業員が会議に参加し、他の人が会議を開始するために来るのを待ちます。また、会議がキャンセルされた場合、彼は退席します:)その後、他の人が現れるのを待たないようにして、患者を失った場合、彼は会議をキャンセルします。

class MeetingAtendeeTheBoss implements Runnable {

CyclicBarrier myMeetingQuorumBarrier;

public MeetingAtendeeTheBoss(CyclicBarrier myMileStoneBarrier) {
    this.myMeetingQuorumBarrier = myMileStoneBarrier;
}

@Override
public void run() {
    try {
        System.out.println(Thread.currentThread().getName() + "I am THE BOSS - i joined the meeting ...");
        //boss dose not like to wait too much!! he/she waits for 2 seconds and we END the meeting
        myMeetingQuorumBarrier.await(1,TimeUnit.SECONDS);
        System.out.println(Thread.currentThread().getName()+" finally meeting stared ...");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        System.out.println("what WHO canceled The meeting");
    } catch (TimeoutException e) {
        System.out.println("These employees waste my time!!");
    }
 }
}

通常の日、従業員は会議に来て、他の人が現れるのを待ちます。参加者がいなければ、無期限に待たなければなりません!いくつかの特別な会議で上司が来て、彼は待つのが好きではありません(5人が会議を開始する必要がありますが、上司と熱心な従業員だけが来ます)。

CyclicBarrier meetingAtendeeQuorum = new CyclicBarrier(5);
Thread atendeeThread = new Thread(new MeetingAtendee(meetingAtendeeQuorum));
Thread atendeeThreadBoss = new Thread(new MeetingAtendeeTheBoss(meetingAtendeeQuorum));
    atendeeThread.start();
    atendeeThreadBoss.start();

出力:

//Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// These employees waste my time!!
// Meeting canceled! every body dance <by chic band!>

別の外部のスレッド(地震)が会議をキャンセルする別のシナリオがあります(コールリセットメソッド)。この場合、すべての待機中のスレッドが例外によって起こされます。

class NaturalDisasters implements Runnable {

CyclicBarrier someStupidMeetingAtendeeQuorum;

public NaturalDisasters(CyclicBarrier someStupidMeetingAtendeeQuorum) {
    this.someStupidMeetingAtendeeQuorum = someStupidMeetingAtendeeQuorum;
}

void earthQuakeHappening(){
    System.out.println("earth quaking.....");
    someStupidMeetingAtendeeQuorum.reset();
}

@Override
public void run() {
    earthQuakeHappening();
 }
}

コードを実行すると、面白い出力になります:

// Thread-1I am THE BOSS - i joined the meeting ...
// Thread-0 i joined the meeting ...
// earth quaking.....
// what WHO canceled The meeting
// Meeting canceled! every body dance <by chic band!>

会議室に秘書を追加することもできます。会議が開催された場合、彼女はすべてのものを文書化しますが、彼女は会議の一部ではありません。

class MeetingSecretary implements Runnable {

@Override
public void run() {
        System.out.println("preparing meeting documents");
        System.out.println("taking notes ...");
 }
}

ラッチ:怒っている上司が企業顧客向けに展示会を開催したい場合、すべてのものを準備する必要があります(リソース)。私たちは、すべての作業者(スレッド)が彼の仕事を投入するためのto-doリストを提供し、to-doリストをチェックします(一部の作業者は塗装を行い、他の作業者はサウンドシステムを準備します...)。to-doリストのすべての項目が完了すると(リソースが提供されます)、お客様への扉を開くことができます。

public class Visitor implements Runnable{

CountDownLatch exhibitonDoorlatch = null;

public Visitor (CountDownLatch latch) {
    exhibitonDoorlatch  = latch;
}

public void run() {
    try {
        exhibitonDoorlatch .await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("customer visiting exebition");
 }
}

そして労働者たちはどのように展示を準備していますか:

class Worker implements Runnable {

CountDownLatch myTodoItem = null;

public Worker(CountDownLatch latch) {
    this.myTodoItem = latch;
}

public void run() {
        System.out.println("doing my part of job ...");
        System.out.println("My work is done! remove it from todo list");
        myTodoItem.countDown();
 }
}

    CountDownLatch preperationTodoList = new CountDownLatch(3);

    // exhibition preparation workers  
    Worker      electricalWorker      = new Worker(preperationTodoList);
    Worker      paintingWorker      = new Worker(preperationTodoList);

    // Exhibition Visitors 
    ExhibitionVisitor exhibitionVisitorA = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorB = new ExhibitionVisitor(preperationTodoList);
    ExhibitionVisitor exhibitionVisitorC = new ExhibitionVisitor(preperationTodoList);

    new Thread(electricalWorker).start();
    new Thread(paintingWorker).start();

    new Thread(exhibitionVisitorA).start();
    new Thread(exhibitionVisitorB).start();
    new Thread(exhibitionVisitorC).start();

6

簡単に言えば、2つの機能の違いを理解するだけです。

public class CountDownLatch {
    private Object mutex = new Object();
    private int count;

    public CountDownLatch(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            while (count > 0) {
                mutex.wait();
            }
        }
    }

    public void countDown() {
        synchronized (mutex) {
            if (--count == 0)
                mutex.notifyAll();
        }

    }
}

そして

public class CyclicBarrier {
    private Object mutex = new Object();
    private int count;

    public CyclicBarrier(int count) {
        this.count = count;
    }

    public void await() throws InterruptedException {
        synchronized (mutex) {
            count--;
            while(count > 0)
                mutex.wait();
            mutex.notifyAll();
        }
    }
}

もちろん、上記の回答で詳細に説明されている、非ブロッキング、時限待機、診断などの機能は除きます。

ただし、上記のクラスは完全に機能し、提供された機能の範囲内で、対応する名前と同等です。

別の言い方をすると、CountDownLatchの内部クラスのサブクラスAQSCyclicBarrier使用中ReentrantLock(私の考えでは、それが逆になっているか、両方がAQSを使用しているか、両方がロックを使用している可能性があります-パフォーマンス効率を損なうことはありません)


5

明らかな違いの1つは、NのCyclicBarrierが1サイクルで解放されるのを待つことができるスレッドはNだけであることです。しかし、無制限の数のスレッドがNのCountDownLatchで待機できます。カウントダウンデクリメントは、1つのスレッドでN回、またはNスレッドで1回ずつ、または組み合わせて実行できます。


4

CyclicBarrierの場合、すべての子スレッドがバリアの呼び出しを開始するとすぐに、バリアでRunnableが実行されます。各子スレッドのバリア。待機は、終了するまでに異なる時間を要し、すべてが同時に終了します。


4

たCountDownLatch、メインスレッドがその実行を完了するために、他のスレッドを待ちます。でCyclicBarrierを、ワーカースレッドは、その実行を完了するために、お互いのを待ちます。

カウントがゼロに達してラッチが開いたら、同じCountDownLatchインスタンスを再利用することはできません。一方、CyclicBarrierは、バリアをリセットしてバリアを解除すると再利用できます。


メインスレッドである必要はありません。CountDownLatchを作成し、それを他の非メインスレッドと共有する任意のスレッドである可能性があります。
Aniket Thakur

1

CountDownLatchは、あらゆるもののカウントダウンです。CyclicBarrierはスレッドのみのカウントダウンです

5つのワーカースレッドと1つの荷送人スレッドがあり、労働者が100アイテムを生産すると、荷送人はそれらを発送します。

CountDownLatchの場合、カウンターはワーカーまたはアイテム上にあります

CyclicBarrierの場合、カウンターはワーカーに対してのみ実行できます

CountDownLatchがアイテムに設定されている状態でワーカーが無限にスリープ状態になると、Shipperは出荷できます。ただし、CyclicBarrierでは、Shipperを呼び出すことはできません。


0

@Kevin Leeと@Jon私はオプションのRunnableでCyclicBarrierを試しました。CyclicBarrierがチップされた後、最初に実行されるように見えます。ここにコードと出力があります

静的CyclicBarrierバリア。

    public static void main(String[] args) throws InterruptedException {
        barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("I run in the beginning and after the CyclicBarrier is tipped");
            }
        });

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);

        System.out.println("Barrier automatically resets.");

        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
        Thread.sleep(1000);
        new Worker().start();
    }

出力

I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
Barrier automatically resets.
I run in the beginning and after the CyclicBarrier is tipped
Let's play.
Let's play.
Let's play.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.