Come viene utilizzato CountDownLatch nel multithreading Java?


184

Qualcuno può aiutarmi a capire cos'è Java CountDownLatche quando usarlo?

Non ho un'idea molto chiara di come funzioni questo programma. A quanto ho capito, tutti e tre i thread iniziano contemporaneamente e ogni thread chiamerà CountDownLatch dopo 3000 ms. Quindi il conto alla rovescia diminuirà uno per uno. Dopo che lo scrocco diventa zero, il programma stampa "Completato". Forse il modo in cui ho capito non è corretto.

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

}


9
Ho appena usato il tuo codice di esempio di domanda per un batch di servizi parallelo Android e ha funzionato come un fascino. Grazie mille!
Roisgoen,

Sono arrivato qui da questo video del 2012, che mostra una notevole somiglianza con l'esempio mostrato qui. Per chiunque sia interessato, questo fa parte di una serie di tutorial multi-threading Java di un ragazzo di nome John. Mi piace John. Altamente raccomandato.
Elia Grady,

Risposte:


194

Sì, hai capito bene. CountDownLatchfunziona secondo il principio del fermo, il filo principale attenderà fino all'apertura del cancello. Un thread attende n thread, specificati durante la creazione di CountDownLatch.

Qualsiasi thread, in genere il thread principale dell'applicazione, le chiamate CountDownLatch.await()attenderanno fino a quando il conteggio raggiunge lo zero o viene interrotto da un altro thread. Tutti gli altri thread devono eseguire il conto alla rovescia chiamando CountDownLatch.countDown()una volta completati o pronti.

Non appena il conteggio raggiunge lo zero, il thread in attesa continua. Uno degli svantaggi / vantaggi di CountDownLatchè che non è riutilizzabile: una volta che il conteggio raggiunge lo zero non è più possibile utilizzarlo CountDownLatch.

Modificare:

Utilizzare CountDownLatchquando un thread (come il thread principale) richiede di attendere il completamento di uno o più thread prima di poter continuare l'elaborazione.

Un classico esempio di utilizzo CountDownLatchin Java è un'applicazione Java lato server che utilizza l'architettura dei servizi, in cui più servizi sono forniti da più thread e l'applicazione non può iniziare l'elaborazione fino a quando tutti i servizi non sono stati avviati correttamente.

La domanda di PS OP ha un esempio piuttosto semplice, quindi non ne ho incluso uno.


1
Grazie per la risposta. Potresti darmi un esempio su dove applicare il blocco CountDown?
amal

11
un tutorial su come usare CountDownLatch è qui howtodoinjava.com/2013/07/18/…
thiagoh

1
@NikolaB Ma in questo dato esempio possiamo ottenere lo stesso risultato usando il metodo join, no?
Vikas Verma,

3
Considererei la non riusabilità un vantaggio: sei sicuro che nessuno può resettarlo o aumentare il conteggio.
ataulm

3
Bella spiegazione. Ma non sarei leggermente d'accordo sul punto One thread waits for n number of threads specified while creating CountDownLatch in Java. Se hai bisogno di tale meccanismo, allora è prudente usare CyclicBarrier. La differenza concettuale fondamentale tra questi due, come indicato in Java concurrency in Practiceè: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()entra in uno stato di blocco.
Rahul Dev Mishra,

43

CountDownLatchin Java è un tipo di sincronizzatore che consente Thread di attendere uno o più Threadsecondi prima che inizi l'elaborazione.

CountDownLatchfunziona secondo il principio del fermo, il thread attenderà fino all'apertura del cancello. Un thread attende il nnumero di thread specificato durante la creazione CountDownLatch.

per esempio final CountDownLatch latch = new CountDownLatch(3);

Qui impostiamo il contatore su 3.

Qualsiasi thread, di solito il thread principale dell'applicazione, che le chiamate CountDownLatch.await()aspetteranno fino a quando il conteggio raggiunge lo zero o viene interrotto da un altro Thread. Tutti gli altri thread devono eseguire il conto alla rovescia chiamando CountDownLatch.countDown()una volta completati o pronti per il lavoro. non appena il conteggio raggiunge lo zero, ilThread attesa inizia a correre.

Qui il conteggio viene diminuito di CountDownLatch.countDown() metodo.

Il Threadche chiama ilawait() metodo attendere raggiunge conta iniziale a zero.

Per contare zero altri thread devono chiamare il countDown()metodo. Una volta che il conteggio diventa zero, il thread che ha invocato il fileawait() metodo riprenderà (inizia la sua esecuzione).

Lo svantaggio di CountDownLatchè che non è riutilizzabile: una volta che il conteggio diventa zero non è più utilizzabile.


usiamo il new CountDownLatch(3)come abbiamo 3 thread dal newFixedThreadPool definito?
Chaklader Asfak Arefe,

"prima che inizi l'elaborazione" non dovrebbe essere "prima che continui l'elaborazione"?
Maria Ines Parnisari,

@Arefe Sì, è il numero di thread che attraversano il tuo blocco di codice
Vishal Akkalkote,

23

NikolaB lo ha spiegato molto bene, tuttavia l'esempio sarebbe utile da capire, quindi ecco un semplice esempio ...

 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

Viene usato quando vogliamo attendere che più di un thread completi il ​​suo compito. È simile al join nei thread.

Dove possiamo usare CountDownLatch

Consideriamo uno scenario in cui sono richiesti tre thread "A", "B" e "C" e vogliamo iniziare il thread "C" solo quando i thread "A" e "B" completano o completano parzialmente il loro compito.

Può essere applicato allo scenario IT del mondo reale

Considerare uno scenario in cui il manager ha diviso i moduli tra i team di sviluppo (A e B) e desidera assegnarlo al team di controllo qualità per i test solo quando entrambi i team completano il proprio compito.

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

L'output del codice sopra sarà:

Attività assegnata al team di sviluppo devB

Attività assegnata al team di sviluppo devA

Compito completato dal team di sviluppo devB

Compito completato dal team di sviluppo devA

Attività assegnata al team di controllo qualità

Compito completato dal team di controllo qualità

Qui attendono () metodo attende bandiera countdownlatch diventino 0, e conto alla rovescia () metodo decrementa bandiera countdownlatch da 1.

Limitazione di JOIN: l' esempio sopra può essere ottenuto anche con JOIN, ma JOIN non può essere utilizzato in due scenari:

  1. Quando utilizziamo ExecutorService invece della classe Thread per creare thread.
  2. Modifica l'esempio sopra in cui Manager vuole consegnare il codice al team QA non appena lo sviluppo completa il suo compito dell'80%. Significa che CountDownLatch ci consente di modificare l'implementazione che può essere utilizzata per attendere un altro thread per la loro esecuzione parziale.

3

CoundDownLatch ti consente di attendere un thread fino a quando tutti gli altri thread non hanno terminato la loro esecuzione.

Lo pseudo codice può essere:

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

Potresti voler spostare tutta la tua descrizione dal blocco di codice
Paul Lo,

Il miglior commento però. Mi piacciono questi commenti "al punto" invece di spiegazioni teoriche.
renatoaraujoc,

2

Un buon esempio di quando usare qualcosa di simile è con Java Simple Serial Connector, che accede alle porte seriali. In genere scriverai qualcosa sulla porta e, in modo asincrono, su un altro thread, il dispositivo risponderà su SerialPortEventListener. In genere, ti consigliamo di mettere in pausa dopo aver scritto sulla porta per attendere la risposta. Gestire manualmente i blocchi di thread per questo scenario è estremamente complicato, ma usare Countdownlatch è facile. Prima di pensare di poterlo fare in un altro modo, fai attenzione alle condizioni di gara che non hai mai pensato !!

pseudocodice:

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

Se aggiungi un po 'di debug dopo la tua chiamata a latch.countDown (), questo potrebbe aiutarti a capirne meglio il comportamento.

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

L'output mostrerà il conteggio in decremento. Questo 'count' è effettivamente il numero di task Runnable (oggetti Processor) che hai avviato contro i quali countDown () non è stato invocato e quindi viene bloccato il thread principale nella sua chiamata a latch.await ().

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

Dalla documentazione di Oracle su CountDownLatch :

Un aiuto per la sincronizzazione che consente ad uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.

A CountDownLatchviene inizializzato con un determinato conteggio. I awaitmetodi si bloccano fino a quando il conteggio corrente raggiunge lo zero a causa di invocazioni del countDown()metodo, dopodiché vengono rilasciati tutti i thread in attesa e le successive invocazioni di attesa vengono restituite immediatamente. Questo è un fenomeno one-shot - il conteggio non può essere resettato.

Un CountDownLatch è uno strumento di sincronizzazione versatile e può essere utilizzato per vari scopi.

UN CountDownLatch inizializzato con un conteggio di uno funge da semplice blocco on / off o gate: tutti i thread che invocano attendono l'attesa al gate fino a quando non viene aperto da un thread che richiama countDown ().

Un CountDownLatchinizializzato su N può essere utilizzato per far attendere un thread fino a quando N thread ha completato alcune azioni o alcune azioni sono state completate N volte.

public void await()
           throws InterruptedException

Fa in modo che il thread corrente attenda fino a quando il latch non viene contato fino a zero, a meno che il thread non venga interrotto.

Se il conteggio corrente è zero, questo metodo restituisce immediatamente.

public void countDown()

Riduce il conteggio del latch, rilasciando tutti i thread in attesa se il conteggio raggiunge lo zero.

Se il conteggio corrente è maggiore di zero, viene diminuito. Se il nuovo conteggio è zero, tutti i thread in attesa vengono riattivati ​​ai fini della pianificazione dei thread.

Spiegazione del tuo esempio.

  1. Hai impostato count come 3 per latchvariabile

    CountDownLatch latch = new CountDownLatch(3);
  2. Hai passato questo condiviso latchal thread di lavoro:Processor

  3. Sono state presentate tre Runnableistanze diProcessorExecutorService executor
  4. Il thread principale ( App) è in attesa che il conteggio diventi zero con l'istruzione seguente

     latch.await();  
  5. Processor il thread dorme per 3 secondi e quindi diminuisce il valore di conteggio latch.countDown()
  6. La prima Processistanza cambierà il conteggio dei latch come 2 dopo il completamento a causa di latch.countDown().

  7. La seconda Processistanza modificherà il conteggio dei latch come 1 dopo il completamento a causa di latch.countDown().

  8. La terza Processistanza cambierà il conteggio dei latch come 0 dopo il completamento a causa di latch.countDown().

  9. Il conteggio zero sul fermo provoca la Appfuoriuscita del filo principaleawait

  10. Il programma dell'app stampa questo output ora: Completed


2

Questo esempio di Java Doc mi ha aiutato a comprendere chiaramente i concetti:

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

Interpretazione visiva:

inserisci qui la descrizione dell'immagine

Evidentemente, CountDownLatchconsente a un thread (qui Driver) di attendere fino a quando un gruppo di thread in esecuzione (qui Worker) è terminato con la sua esecuzione.


1

Come menzionato in JavaDoc ( https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html ), CountDownLatch è un aiuto per la sincronizzazione, introdotto in Java 5. Qui la sincronizzazione non funziona significa limitare l'accesso a una sezione critica. Ma piuttosto azioni di sequenziamento di thread diversi. Il tipo di sincronizzazione raggiunto tramite CountDownLatch è simile a quello di Join. Supponiamo che ci sia un thread "M" che deve attendere che altri thread di lavoro "T1", "T2", "T3" per completare i suoi compiti Prima di Java 1.5, il modo in cui questo può essere fatto è, M che esegue il seguente codice

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

Il codice sopra si assicura che il thread M riprenda il suo lavoro dopo che T1, T2, T3 hanno completato il suo lavoro. T1, T2, T3 possono completare il loro lavoro in qualsiasi ordine. Lo stesso può essere ottenuto tramite CountDownLatch, dove T1, T2, T3 e thread M condividono lo stesso oggetto CountDownLatch.
Richieste "M": countDownLatch.await();
dove sono "T1", "T2", "T3" countDownLatch.countdown();

Uno svantaggio con il metodo join è che M deve conoscere T1, T2, T3. Se in seguito viene aggiunto un nuovo thread di lavoro T4, anche M deve esserne consapevole. Questo può essere evitato con CountDownLatch. Dopo l'implementazione la sequenza di azione sarebbe [T1, T2, T3] (l'ordine di T1, T2, T3 potrebbe essere comunque) -> [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();
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.