Wie wird CountDownLatch in Java Multithreading verwendet?


183

Kann mir jemand helfen zu verstehen, was Java CountDownLatchist und wann ich es verwenden soll?

Ich habe keine klare Vorstellung davon, wie dieses Programm funktioniert. Soweit ich weiß, beginnen alle drei Threads gleichzeitig und jeder Thread ruft nach 3000 ms CountDownLatch auf. Der Countdown wird also nacheinander verringert. Nachdem die Verriegelung Null geworden ist, gibt das Programm "Abgeschlossen" aus. Vielleicht ist die Art, wie ich verstanden habe, falsch.

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
Ich habe gerade Ihren Fragen-Beispielcode für einen Android-Parallel-Service-Batch verwendet und es hat wie ein Zauber funktioniert. Ich danke dir sehr!
Roisgoen

Ich bin von diesem Video aus dem Jahr 2012 hierher gekommen , das eine bemerkenswerte Ähnlichkeit mit dem hier gezeigten Beispiel zeigt. Für alle Interessierten ist dies Teil einer Java-Multithreading-Tutorialserie von einem Typen namens John. Ich mag John. Sehr empfehlenswert.
Elia Grady

Antworten:


194

Ja, du hast richtig verstanden. CountDownLatchfunktioniert nach dem Latch-Prinzip, der Hauptfaden wartet, bis das Tor geöffnet ist. Ein Thread wartet auf n Threads, die beim Erstellen des Threads angegeben wurden CountDownLatch.

Jeder Thread, normalerweise der Hauptthread der Anwendung, der aufruft CountDownLatch.await(), wartet, bis die Anzahl Null erreicht oder von einem anderen Thread unterbrochen wird. Alle anderen Threads müssen durch Aufrufen heruntergezählt werden, CountDownLatch.countDown()sobald sie abgeschlossen oder bereit sind.

Sobald die Zählung Null erreicht, wird der wartende Thread fortgesetzt. Einer der Nachteile / Vorteile von CountDownLatchist, dass es nicht wiederverwendbar ist: Sobald die Zählung Null erreicht, können Sie nicht CountDownLatchmehr verwenden.

Bearbeiten:

Verwenden Sie diese Option, CountDownLatchwenn ein Thread (wie der Haupt-Thread) warten muss, bis ein oder mehrere Threads abgeschlossen sind, bevor die Verarbeitung fortgesetzt werden kann.

Ein klassisches Beispiel für die Verwendung CountDownLatchin Java ist eine serverseitige Java-Kernanwendung, die eine Dienstarchitektur verwendet, bei der mehrere Dienste von mehreren Threads bereitgestellt werden und die Anwendung erst mit der Verarbeitung beginnen kann, wenn alle Dienste erfolgreich gestartet wurden.

Die Frage von PS OP hat ein ziemlich einfaches Beispiel, daher habe ich keines aufgenommen.


1
Danke für deine Antwort. Können Sie mir ein Beispiel geben, wo CountDown-Latch angewendet werden soll?
Amal

11
Ein Tutorial zur Verwendung von CountDownLatch finden Sie hier howtodoinjava.com/2013/07/18/…
thiagoh

1
@NikolaB Aber in diesem Beispiel können wir das gleiche Ergebnis mit der Join-Methode erzielen, nicht wahr?
Vikas Verma

3
Ich würde die Nicht-Wiederverwendbarkeit als Vorteil betrachten: Sie sind sicher, dass niemand sie zurücksetzen oder die Anzahl erhöhen kann.
Ataulm

3
Schöne Erklärung. Aber ich würde in diesem Punkt etwas anderer Meinung sein One thread waits for n number of threads specified while creating CountDownLatch in Java. Wenn Sie einen solchen Mechanismus benötigen, ist es ratsam, ihn zu verwenden CyclicBarrier. Der grundlegende konzeptionelle Unterschied zwischen diesen beiden, wie in angegeben, Java concurrency in Practiceist : Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()geht in einen blockierenden Zustand.
Rahul Dev Mishra

43

CountDownLatchIn Java handelt es sich um eine Art Synchronisierer, mit dem man Thread auf ein oder mehrere ThreadSekunden warten kann, bevor die Verarbeitung beginnt.

CountDownLatchfunktioniert nach dem Latch-Prinzip, der Thread wartet, bis das Gate geöffnet ist. Ein Thread wartet auf die nAnzahl der beim Erstellen angegebenen Threads CountDownLatch.

z.B final CountDownLatch latch = new CountDownLatch(3);

Hier setzen wir den Zähler auf 3.

Jeder Thread, normalerweise der Hauptthread der Anwendung, der aufruft CountDownLatch.await(), wartet, bis die Anzahl Null erreicht oder von einem anderen unterbrochen wird Thread. Alle anderen Threads müssen einen Countdown durchführen, indem sie aufrufen, CountDownLatch.countDown()sobald sie abgeschlossen oder für den Job bereit sind. Sobald die Zählung Null erreicht, Threadbeginnt das Warten.

Hier wird die Anzahl nach CountDownLatch.countDown()Methode dekrementiert .

Der ThreadAufruf der await()Methode wartet, bis die anfängliche Zählung Null erreicht.

Um die Zählung auf Null zu setzen, müssen andere Threads die countDown()Methode aufrufen . Sobald die Anzahl Null wird, wird der Thread, der die await()Methode aufgerufen hat , fortgesetzt (Start der Ausführung).

Der Nachteil von CountDownLatchist, dass es nicht wiederverwendbar ist: Sobald der Zähler Null wird, ist es nicht mehr verwendbar.


Verwenden wir das, new CountDownLatch(3)da wir 3 Threads aus dem newFixedThreadPool definierten haben?
Chaklader Asfak Arefe

sollte "bevor die Verarbeitung beginnt" nicht "bevor die Verarbeitung fortgesetzt wird" sein?
Maria Ines Parnisari

@Arefe Ja, es ist die Anzahl der Threads, die durch Ihren Codeblock gehen
Vishal Akkalkote

23

NikolaB hat es sehr gut erklärt. Ein Beispiel wäre jedoch hilfreich zu verstehen. Hier ist ein einfaches Beispiel ...

 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

Es wird verwendet, wenn mehr als ein Thread warten soll, um seine Aufgabe abzuschließen. Es ist ähnlich, Threads zu verbinden.

Wo wir CountDownLatch verwenden können

Stellen Sie sich ein Szenario vor, in dem drei Threads "A", "B" und "C" erforderlich sind und der Thread "C" nur gestartet werden soll, wenn die Threads "A" und "B" ihre Aufgabe abgeschlossen oder teilweise abgeschlossen haben.

Es kann auf ein reales IT-Szenario angewendet werden

Stellen Sie sich ein Szenario vor, in dem der Manager Module zwischen Entwicklungsteams (A und B) aufteilt und sie dem QA-Team nur dann zum Testen zuweisen möchte, wenn beide Teams ihre Aufgabe abgeschlossen haben.

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");
    }
}

Die Ausgabe des obigen Codes lautet:

Aufgabe dem Entwicklungsteam devB zugewiesen

Aufgabe dem Entwicklungsteam devA zugewiesen

Aufgabe vom Entwicklungsteam devB erledigt

Aufgabe vom Entwicklungsteam devA erledigt

Aufgabe dem QA-Team zugewiesen

Aufgabe vom QA-Team erledigt

Hier wartet die Methode await () darauf , dass das Countdownlatch-Flag 0 wird, und die countDown () -Methode verringert das Countdownlatch-Flag um 1.

Einschränkung von JOIN: Das obige Beispiel kann auch mit JOIN erreicht werden, JOIN kann jedoch nicht in zwei Szenarien verwendet werden:

  1. Wenn wir ExecutorService anstelle der Thread-Klasse verwenden, um Threads zu erstellen.
  2. Ändern Sie das obige Beispiel, in dem der Manager Code an das QA-Team übergeben möchte, sobald die Entwicklung ihre 80% -Aufgabe abgeschlossen hat. Dies bedeutet, dass wir mit CountDownLatch die Implementierung ändern können, mit der auf einen anderen Thread für dessen teilweise Ausführung gewartet werden kann.

3

Mit CoundDownLatch können Sie einen Thread warten lassen, bis alle anderen Threads mit ihrer Ausführung fertig sind.

Pseudocode kann sein:

// 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

Vielleicht möchten Sie Ihre gesamte Beschreibung aus dem Codeblock entfernen
Paul Lo

Bester Kommentar. Ich mag diese "auf den Punkt" -Kommentare anstelle von theoretischen Erklärungen.
Renatoaraujoc

2

Ein gutes Beispiel für die Verwendung von so etwas ist der Java Simple Serial Connector, der auf serielle Schnittstellen zugreift. Normalerweise schreiben Sie etwas in den Port, und asynchron in einem anderen Thread antwortet das Gerät auf einem SerialPortEventListener. Normalerweise möchten Sie nach dem Schreiben an den Port eine Pause einlegen, um auf die Antwort zu warten. Die manuelle Behandlung der Thread-Sperren für dieses Szenario ist äußerst schwierig, die Verwendung von Countdownlatch ist jedoch einfach. Bevor Sie denken, dass Sie es anders machen können, achten Sie auf die Rennbedingungen, an die Sie nie gedacht haben !!

Pseudocode:

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

Wenn Sie nach Ihrem Aufruf von latch.countDown () ein Debug hinzufügen, kann dies Ihnen helfen, das Verhalten besser zu verstehen.

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

Die Ausgabe zeigt den dekrementierten Count an. Diese 'Anzahl' ist effektiv die Anzahl der ausführbaren Aufgaben (Prozessorobjekte), die Sie gestartet haben und für die countDown () nicht aufgerufen wurde. Daher wird der Hauptthread beim Aufruf von latch.await () blockiert.

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

Aus der Oracle-Dokumentation zu CountDownLatch :

Eine Synchronisationshilfe, mit der ein oder mehrere Threads warten können, bis eine Reihe von Vorgängen in anderen Threads abgeschlossen ist.

A CountDownLatchwird mit einer bestimmten Anzahl initialisiert. Die awaitMethoden blockieren, bis die aktuelle Anzahl aufgrund von Aufrufen der countDown()Methode Null erreicht. Danach werden alle wartenden Threads freigegeben und alle nachfolgenden Aufrufe von wait warten sofort zurück. Dies ist ein One-Shot-Phänomen - die Zählung kann nicht zurückgesetzt werden.

Ein CountDownLatch ist ein vielseitiges Synchronisationswerkzeug und kann für eine Reihe von Zwecken verwendet werden.

Ein CountDownLatchmit einer Zählung von eins initialisiertes dient als einfaches Ein / Aus-Latch oder Gate: Alle aufrufenden Threads warten auf das Warten am Gate, bis es von einem Thread geöffnet wird, der countDown () aufruft.

Eine CountDownLatchauf N initialisierte Option kann verwendet werden, um einen Thread warten zu lassen, bis N Threads eine Aktion abgeschlossen haben oder eine Aktion N-mal abgeschlossen wurde.

public void await()
           throws InterruptedException

Bewirkt, dass der aktuelle Thread wartet, bis der Latch auf Null heruntergezählt wurde, es sei denn, der Thread wird unterbrochen.

Wenn die aktuelle Anzahl Null ist, kehrt diese Methode sofort zurück.

public void countDown()

Verringert die Anzahl der Latches und gibt alle wartenden Threads frei, wenn die Anzahl Null erreicht.

Wenn die aktuelle Anzahl größer als Null ist, wird sie dekrementiert. Wenn die neue Anzahl Null ist, werden alle wartenden Threads für Thread-Planungszwecke wieder aktiviert.

Erklärung Ihres Beispiels.

  1. Sie haben die Anzahl für die latchVariable auf 3 gesetzt

    CountDownLatch latch = new CountDownLatch(3);
  2. Sie haben diese Freigabe latchan den Worker-Thread übergeben:Processor

  3. Drei RunnableInstanzen von Processorwurden eingereichtExecutorService executor
  4. Der Haupt-Thread ( App) wartet darauf, dass die Anzahl mit der folgenden Anweisung Null wird

     latch.await();  
  5. Processor Der Thread schläft 3 Sekunden lang und verringert dann den Zählwert mit latch.countDown()
  6. Die erste ProcessInstanz ändert die Anzahl der Latches nach Abschluss aufgrund von latch.countDown().

  7. Die zweite ProcessInstanz ändert die Anzahl der Latches nach Abschluss aufgrund von 1 latch.countDown().

  8. Die dritte ProcessInstanz ändert die Anzahl der Latches nach Abschluss aufgrund von auf 0 latch.countDown().

  9. Die Nullzählung bei der Verriegelung bewirkt App, dass der Hauptfaden herauskommtawait

  10. Das App-Programm druckt diese Ausgabe jetzt: Completed


2

Dieses Beispiel aus Java Doc hat mir geholfen, die Konzepte klar zu verstehen:

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() { ... }
}

Visuelle Interpretation:

Geben Sie hier die Bildbeschreibung ein

Offensichtlich CountDownLatchkann ein Thread (hier Driver) warten, bis eine Reihe laufender Threads (hier Worker) mit ihrer Ausführung fertig sind.


1

Wie in JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ) erwähnt, ist CountDownLatch eine in Java 5 eingeführte Synchronisationshilfe. Hier erfolgt die Synchronisation nicht bedeutet, den Zugang zu einem kritischen Abschnitt einzuschränken. Sondern Sequenzierung von Aktionen verschiedener Threads. Die Art der Synchronisation, die durch CountDownLatch erreicht wird, ähnelt der von Join. Angenommen, es gibt einen Thread "M", der warten muss, bis andere Arbeitsthreads "T1", "T2", "T3" seine Aufgaben erledigt haben. Vor Java 1.5 kann dies folgendermaßen ausgeführt werden: M führt den folgenden Code aus

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

Der obige Code stellt sicher, dass Thread M seine Arbeit wieder aufnimmt, nachdem T1, T2, T3 seine Arbeit abgeschlossen haben. T1, T2, T3 können ihre Arbeit in beliebiger Reihenfolge erledigen. Dasselbe kann durch CountDownLatch erreicht werden, wobei T1, T2, T3 und Thread M dasselbe CountDownLatch-Objekt verwenden.
"M" -Anfragen: countDownLatch.await();
wo wie "T1", "T2", "T3" countDownLatch.countdown();

Ein Nachteil bei der Join-Methode ist, dass M über T1, T2, T3 Bescheid wissen muss. Wenn später ein neuer Arbeitsthread T4 hinzugefügt wird, muss sich auch M dessen bewusst sein. Dies kann mit CountDownLatch vermieden werden. Nach der Implementierung wäre die Aktionssequenz [T1, T2, T3] (die Reihenfolge von T1, T2, T3 könnte sowieso sein) -> [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();
        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.