CountDownLatchはJavaマルチスレッドでどのように使用されますか?


183

誰かが私にJava CountDownLatchとは何か、いつそれを使用するかを理解するのを手伝ってくれる?

このプログラムがどのように機能するのか、私にはよくわかりません。私が理解しているように、3つのスレッドすべてが一度に開始し、各スレッドは3000ms後にCountDownLatchを呼び出します。したがって、カウントダウンは1つずつ減少します。ラッチがゼロになった後、プログラムは「完了」と出力します。多分私が理解した方法は間違っています。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class Processor implements Runnable {
    private CountDownLatch latch;

    public Processor(CountDownLatch latch) {
        this.latch = latch;
    }

    public void run() {
        System.out.println("Started.");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        latch.countDown();
    }
}

// ------------------------------------------------ -----

public class App {

    public static void main(String[] args) {

        CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0

        ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool

        for(int i=0; i < 3; i++) {
            executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
        }

        try {
            latch.await();  // wait until latch counted down to 0
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Completed.");
    }

}


8
私はちょうどあなたの質問のサンプルコードをAndroid並列サービスバッチに使用しましたが、それは魅力のように機能しました。どうもありがとうございます!
Roisgoen

ここからもらったこのビデオを示す2012年から、顕著な類似ここに示した例とします。これは、Johnという名前の男によるJavaマルチスレッドチュートリアルシリーズの一部です。私はジョンが好きです。強くお勧めします。
エリアグラディ

回答:


194

はい、あなたは正しく理解しました。 CountDownLatchラッチ原理で動作し、メインスレッドはゲートが開くまで待機します。1つのスレッドは、の作成中に指定されたnスレッドを待機しますCountDownLatch

スレッドは、通常はアプリケーションのメインスレッドであり、CountDownLatch.await()countが0になるか、別のスレッドによって中断されるまで待機します。他のすべてのスレッドはCountDownLatch.countDown()、完了または準備ができたら呼び出してカウントダウンする必要があります。

カウントがゼロに達するとすぐに、待機中のスレッドが続行します。の欠点/利点の1つは、CountDownLatch再利用できないことCountDownLatchです。カウントがゼロに達すると、それ以上使用できなくなります。

編集:

使用CountDownLatch(メインスレッドのような)1つのスレッドが処理を続行する前に、完了するまでに、1つ以上のスレッドを待つことが必要です。

CountDownLatchJava で使用する古典的な例は、サービスアーキテクチャを使用するサーバー側コアJavaアプリケーションです。このアプリケーションでは、複数のサービスが複数のスレッドによって提供され、アプリケーションはすべてのサービスが正常に開始されるまで処理を開始できません。

PS OPの質問にはかなり単純な例があるため、ここには含めませんでした。


1
返信ありがとうございます。CountDownラッチを適用する場所の例を教えてください。
2013

11
たCountDownLatchを使用する方法のチュートリアルはこちらhowtodoinjava.com/2013/07/18/...
thiagoh

1
@NikolaBしかし、この例では、結合メソッドを使用して同じ結果を得ることができますね。
Vikas Verma 2014

3
非再利用性を利点と考えます。誰もそれをリセットしたり、カウントを増やしたりすることはできないはずです。
ataulm 2015年

3
いい説明。しかし、私はその点については少し同意しませんOne thread waits for n number of threads specified while creating CountDownLatch in Java。このようなメカニズムが必要な場合は、を使用するのが賢明CyclicBarrierです。に示すように、これら2つの間の基本的な概念の違いは次のJava concurrency in PracticeとおりLatches are for waiting for events; barriers are for waiting for other threadsです。cyclicBarrier.await()ブロッキング状態になります。
Rahul Dev Mishra 2017年

43

CountDownLatchJavaでは、シンクロナイザーの一種であり、処理を開始する前にThread 1つ以上Threadのs を待つことができます。

CountDownLatchラッチ原理で動作し、スレッドはゲートが開くまで待機します。1つnのスレッドは、作成中に指定された数のスレッドを待機しますCountDownLatch

例えば final CountDownLatch latch = new CountDownLatch(3);

ここでは、カウンターを3に設定します。

スレッドは、通常はアプリケーションのメインスレッドであり、CountDownLatch.await()countがゼロに達するか、別のスレッドによって中断されるまで待機しますThread。他のすべてのスレッドはCountDownLatch.countDown()、完了するか、ジョブの準備ができたら、呼び出してカウントダウンする必要があります。カウントがゼロに達するとすぐに、Thread待機が実行を開始します。

ここでは、CountDownLatch.countDown()メソッドによってカウントが減少します。

Thread呼び出すawait()メソッドは、ゼロに初期カウントに到達するまで待機します。

カウントをゼロにするには、他のスレッドが countDown()メソッド。カウントがゼロになると、await()メソッドを呼び出したスレッドが再開します(実行を開始します)。

の欠点CountDownLatchは、再利用できないことです。カウントがゼロになると、使用できなくなります。


我々は、使用しないnew CountDownLatch(3)、我々はから3つのスレッド持っているようにnewFixedThreadPool 定義されましたの?
Chaklader Asfak Arefe

「処理を開始する前」を「処理を続行する前」にすべきではありませんか?
マリアイネスパルニサリ2018

@Arefeはい、コードのブロックを通過するスレッドの数です
Vishal Akkalkote 2018

23

NikolaBはそれを非常によく説明しました、しかし例は理解するのに役立ちます、それでここに一つの簡単な例があります...

 import java.util.concurrent.*;


  public class CountDownLatchExample {

  public static class ProcessThread implements Runnable {

    CountDownLatch latch;
    long workDuration;
    String name;

    public ProcessThread(String name, CountDownLatch latch, long duration){
        this.name= name;
        this.latch = latch;
        this.workDuration = duration;
    }


    public void run() {
        try {
            System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
            Thread.sleep(workDuration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+ "completed its works");
        //when task finished.. count down the latch count...

        // basically this is same as calling lock object notify(), and object here is latch
        latch.countDown();
    }
}


public static void main(String[] args) {
    // Parent thread creating a latch object
    CountDownLatch latch = new CountDownLatch(3);

    new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
    new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
    new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs


    System.out.println("waiting for Children processes to complete....");
    try {
        //current thread will get notified if all chidren's are done 
        // and thread will resume from wait() mode.
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("All Process Completed....");

    System.out.println("Parent Thread Resuming work....");



     }
  }

22

これは、複数のスレッドがタスクを完了するのを待つときに使用されます。スレッドでの結合に似ています。

CountDownLatchを使用できる場所

「A」、「B」、「C」の3つのスレッドがあり、「A」と「B」のスレッドがタスクを完了または部分的に完了したときにのみスレッド「C」を開始したいというシナリオがあるとします。

現実のITシナリオに適用できます

マネージャーが開発チーム(AとB)間でモジュールを分割し、両方のチームがタスクを完了したときにのみテストするために、QAチームにモジュールを割り当てたいシナリオを考えます。

public class Manager {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(2);
        MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
        MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
        teamDevA.start();
        teamDevB.start();
        countDownLatch.await();
        MyQATeam qa = new MyQATeam();
        qa.start();
    }   
}

class MyDevTeam extends Thread {   
    CountDownLatch countDownLatch;
    public MyDevTeam (CountDownLatch countDownLatch, String name) {
        super(name);
        this.countDownLatch = countDownLatch;       
    }   
    @Override
    public void run() {
        System.out.println("Task assigned to development team " + Thread.currentThread().getName());
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
                ex.printStackTrace();
        }
    System.out.println("Task finished by development team Thread.currentThread().getName());
            this.countDownLatch.countDown();
    }
}

class MyQATeam extends Thread {   
    @Override
    public void run() {
        System.out.println("Task assigned to QA team");
        try {
                Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("Task finished by QA team");
    }
}

上記のコードの出力は次のようになります。

開発チームdevBに割り当てられたタスク

開発チームdevAに割り当てられたタスク

開発チームdevBが終了したタスク

開発チームdevAが終了したタスク

QAチームに割り当てられたタスク

QAチームが完了したタスク

ここで、await()メソッドはcountdownlatchフラグが0になるのを待機し、countDown()メソッドはcountdownlatchフラグを1だけ減らします。

JOINの制限: 上記の例はJOINでも実現できますが、JOINは次の2つのシナリオでは使用できません。

  1. Threadクラスの代わりにExecutorServiceを使用してスレッドを作成する場合。
  2. 上記の例を変更して、Managerが開発が80%のタスクを完了するとすぐにコードをQAチームに引き渡そうとしています。これは、CountDownLatchを使用して、別のスレッドが部分的に実行されるのを待つために使用できる実装を変更できることを意味します。

3

CoundDownLatchを使用すると、他のすべてのスレッドの実行が完了するまでスレッドを待機させることができます。

疑似コードは次のとおりです。

// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution

コードブロックからすべての説明を削除することもできます
Paul Lo

でも最高のコメント。理論的な説明ではなく、これらの「要点」のコメントが好きです。
renatoaraujoc 2015年

2

このようなものを使用する良い例の1つは、シリアルポートにアクセスするJava Simple Serial Connectorです。通常は、何かをポートに書き込み、非同期的に別のスレッドで、デバイスはSerialPortEventListenerで応答します。通常、ポートに書き込んだ後、応答を待つために一時停止する必要があります。このシナリオのスレッドロックを手動で処理するのは非常に困難ですが、Countdownlatchの使用は簡単です。あなたが別の方法でそれを行うことができると考える前に、あなたが考えたことのないレース状態に注意してください!!

疑似コード:

CountDownLatch latch;
void writeData() { 
   latch = new CountDownLatch(1);
   serialPort.writeBytes(sb.toString().getBytes())
   try {
      latch.await(4, TimeUnit.SECONDS);
    } catch (InterruptedException e) {
   }
}
class SerialPortReader implements SerialPortEventListener {
    public void serialEvent(SerialPortEvent event) {
        if(event.isRXCHAR()){//If data is available
            byte buffer[] = serialPort.readBytes(event.getEventValue());
            latch.countDown();
         }
     }
}


2

lamp.countDown()の呼び出しの後にデバッグを追加すると、その動作をよりよく理解するのに役立ちます。

latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug

出力には、カウントが減少していることが示されます。この「カウント」は、countDown()が呼び出されていないために開始したRunnableタスク(Processorオブジェクト)の数であり、メインのスレッドがラッチのラッチでブロックされます。

DONE java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE java.util.concurrent.CountDownLatch@70e69696[Count = 0]

2

CountDownLatchに関するoracleドキュメントから:

1つ以上のスレッドが、他のスレッドで実行されている一連の操作が完了するまで待機することを可能にする同期支援。

A CountDownLatchは指定されたカウントで初期化されます。await現在のカウントに達するまで、メソッドのブロックはの呼び出しのためにゼロcountDown()待機中のすべてのスレッドがただちに解放とのawaitリターンの任意の後続の呼び出しされた後の方法。これはワンショット現象です。カウントをリセットすることはできません。

CountDownLatchは多用途の同期ツールであり、さまざまな目的で使用できます。

CountDownLatchラッチ、またはゲートのオン/オフ、単純なように、1つのサーブのカウントで初期化:それはカウントダウンを()を呼び出すスレッドによって開かれるまで、すべてのスレッドがゲートでのawait待機を呼び出します。

CountDownLatchNに初期化は、N個のスレッドが何らかのアクションを完了している、または何らかのアクションがN回完了するまで、一つのスレッドを待機させるために使用することができます。

public void await()
           throws InterruptedException

スレッドが中断されない限り、ラッチがゼロまでカウントダウンするまで現在のスレッドを待機させます。

現在のカウントがゼロの場合、このメソッドはすぐに戻ります。

public void countDown()

ラッチのカウントを減らし、カウントがゼロに達すると、待機中のスレッドをすべて解放します。

現在のカウントがゼロより大きい場合は、減らされます。新しいカウントがゼロの場合、待機中のすべてのスレッドがスレッドスケジューリングの目的で再び有効になります。

あなたの例の説明。

  1. latch変数のカウントを3に設定しました

    CountDownLatch latch = new CountDownLatch(3);
  2. これを共有しました latchをワーカースレッドにた:Processor

  3. の3つのRunnableインスタンスProcessorが送信されましたExecutorService executor
  4. メインスレッド(App)は、以下のステートメントでカウントがゼロになるのを待機しています

     latch.await();  
  5. Processor スレッドは3秒間スリープし、次にカウント値を latch.countDown()
  6. 最初のProcessインスタンスは、完了後にラッチカウントを2に変更します。latch.countDown()ます。

  7. 2番目のProcessインスタンスは、による完了後にラッチカウントを1に変更しlatch.countDown()ます。

  8. 3番目のProcessインスタンスは、による完了後にラッチカウントを0に変更しlatch.countDown()ます。

  9. ラッチのカウントがゼロの場合、メインスレッドAppawait

  10. アプリプログラムはこの出力を今印刷します: Completed


2

このJava Docの例は、概念を明確に理解するのに役立ちました。

class Driver { // ...
  void main() throws InterruptedException {
    CountDownLatch startSignal = new CountDownLatch(1);
    CountDownLatch doneSignal = new CountDownLatch(N);

    for (int i = 0; i < N; ++i) // create and start threads
      new Thread(new Worker(startSignal, doneSignal)).start();

    doSomethingElse();            // don't let run yet
    startSignal.countDown();      // let all threads proceed
    doSomethingElse();
    doneSignal.await();           // wait for all to finish
  }
}

class Worker implements Runnable {
  private final CountDownLatch startSignal;
  private final CountDownLatch doneSignal;
  Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
     this.startSignal = startSignal;
     this.doneSignal = doneSignal;
  }
  public void run() {
     try {
       startSignal.await();
       doWork();
       doneSignal.countDown();
     } catch (InterruptedException ex) {} // return;
  }

  void doWork() { ... }
}

視覚的解釈:

ここに画像の説明を入力してください

明らかに、CountDownLatch1つのスレッド(ここDriver)は、実行中のスレッド(ここWorker)の実行が完了するまで待機します。


1

JavaDoc(https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)で述べられているように、CountDownLatchはJava 5で導入された同期補助です。ここでは、同期は行われません。重要なセクションへのアクセスを制限することを意味します。しかし、むしろ異なるスレッドのアクションを順序付けます。CountDownLatchを介して達成される同期のタイプは、Joinのそれに似ています。他のワーカースレッド「T1」、「T2」、「T3」がタスクを完了するのを待つ必要があるスレッド「M」があると想定します。Java1.5より前のバージョンでは、Mは次のコードを実行します。

    T1.join();
    T2.join();
    T3.join();

上記のコードは、T1、T2、T3が作業を完了した後にスレッドMが確実に作業を再開するようにします。T1、T2、T3は任意の順序で作業を完了することができます。T1、T2、T3とスレッドMが同じCountDownLatchオブジェクトを共有するCountDownLatchを使用しても同じことができます。
「M」リクエスト: countDownLatch.await();
「T1」、「T2」、「T3」のように countDownLatch.countdown();

結合方法の1つの欠点は、MがT1、T2、T3について知っている必要があることです。後で追加される新しいワーカースレッドT4がある場合、Mもそれを認識する必要があります。これは、CountDownLatchで回避できます。実装後、アクションのシーケンスは[T1、T2、T3]になります(T1、T2、T3の順序はいずれにしてもかまいません)-> [M]



0
package practice;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch c= new CountDownLatch(3);  // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method 
        Task t = new Task(c);
        Task t1 = new Task(c);
        Task t2 = new Task(c);
        t.start();
        t1.start();
        t2.start();
        c.await(); // when count becomes zero main thread will wake up 
        System.out.println("This will print after count down latch count become zero");
    }
}

class Task extends Thread{
    CountDownLatch c;

    public Task(CountDownLatch c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(1000);
            c.countDown();   // each thread decrement the count by one 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.