web-dev-qa-db-fra.com

Comment CountDownLatch est-il utilisé dans Java Multithreading?

Quelqu'un peut-il m'aider à comprendre ce qu'est Java CountDownLatch et à quel moment l'utiliser?

Je n'ai pas une idée très claire du fonctionnement de ce programme. Si je comprends bien, les trois threads commencent en même temps et chaque thread appellera CountDownLatch après 3000 ms. Donc, compte à rebours décrémenter un par un. Lorsque le verrouillage est à zéro, le programme imprime "Completed". Peut-être que la façon dont j'ai compris est incorrecte.

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

}
168
amal

Oui, vous avez bien compris CountDownLatch fonctionne dans le principe du verrou, le thread principal attendra que le portail soit ouvert. Un thread attend n threads, spécifiés lors de la création de CountDownLatch.

Tout thread, généralement le thread principal de l'application, appelant CountDownLatch.await() attendra que le compte atteigne zéro ou qu'il soit interrompu par un autre thread. Tous les autres threads doivent décompter en appelant CountDownLatch.countDown() une fois qu'ils sont terminés ou prêts.

Dès que le compte atteint zéro, le thread en attente continue. L'un des inconvénients/avantages de CountDownLatch est qu'il n'est pas réutilisable: une fois le nombre atteint zéro, vous ne pouvez plus utiliser CountDownLatch.

Edit:

Utilisez CountDownLatch lorsqu'un thread (comme le thread principal) doit attendre qu'un ou plusieurs threads soient terminés avant de pouvoir poursuivre le traitement.

Un exemple classique d'utilisation de CountDownLatch dans Java est une application principale côté serveur Java qui utilise une architecture de services, dans laquelle plusieurs services sont fournis par plusieurs threads et l'application ne peut pas démarrer le traitement. jusqu'à ce que tous les services aient démarré avec succès.

P.S. La question d'OP a un exemple assez simple, donc je n'en ai pas inclus.

181
NikolaB

CountDownLatch dans Java est un type de synchroniseur qui permet à un Thread d'attendre un ou plusieurs Threads avant le début du traitement.

CountDownLatch fonctionne sur le principe du verrou, le fil attendra que la porte soit ouverte. Un thread attend n nombre de threads spécifiés lors de la création de CountDownLatch.

par exemple. final CountDownLatch latch = new CountDownLatch(3);

Ici, nous plaçons le compteur à 3.

Tout thread, généralement le thread principal de l'application, qui appelle CountDownLatch.await() attendra que le compte atteigne zéro ou qu'il soit interrompu par un autre Thread. Tous les autres threads doivent effectuer un décompte en appelant CountDownLatch.countDown() une fois qu'ils sont terminés ou prêts à l'emploi. dès que le compte atteint zéro, la Thread en attente commence à s'exécuter.

Ici, le nombre est décrémenté par la méthode CountDownLatch.countDown().

La Thread qui appelle la méthode await() attendra que le nombre initial atteigne zéro.

Pour que rien ne soit compté, les autres threads doivent appeler la méthode countDown(). Une fois que le compte est devenu zéro, le thread qui a appelé la méthode await() reprendra (débutera son exécution).

L'inconvénient de CountDownLatch est qu'il n'est pas réutilisable: une fois que le compte est devenu zéro, il n'est plus utilisable.

37
Vishal Akkalkote

NikolaB l'a très bien expliqué. Cependant, il serait utile de comprendre un exemple. Voici donc un exemple simple ...

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



     }
  }
21
vikashait

Il est utilisé lorsque nous voulons attendre que plusieurs threads aient terminé leur tâche. C'est similaire à rejoindre dans les discussions.

Où on peut utiliser CountDownLatch

Prenons un scénario dans lequel nous avons une exigence dans laquelle nous avons trois threads "A", "B" et "C" et nous voulons démarrer le thread "C" uniquement lorsque les threads "A" et "B" terminent ou complètent partiellement leur tâche.

Il peut être appliqué à un scénario informatique réel

Prenons un scénario dans lequel le responsable divise les modules entre les équipes de développement (A et B) et souhaite l’assigner à l’équipe d’Assurance qualité pour qu’il ne teste que lorsque les deux équipes ont terminé leur tâche.

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

La sortie du code ci-dessus sera:

Tâche assignée à l'équipe de développement devB

Tâche assignée à l'équipe de développement devA

Tâche terminée par l'équipe de développement devB

Tâche terminée par l'équipe de développement devA

Tâche assignée à l'équipe d'assurance qualité

Tâche terminée par l'équipe QA

Ici wait () la méthode attend que l'indicateur de compte à rebours devienne 0 et countDown () la méthode décrémente l'indicateur de compte à rebours de 1.

Limitation de JOIN: L'exemple ci-dessus peut également être atteint avec JOIN, mais JOIN ne peut pas être utilisé dans deux scénarios:

  1. Lorsque nous utilisons ExecutorService au lieu de la classe Thread pour créer des threads.
  2. Modifiez l'exemple ci-dessus dans lequel Manager souhaite transférer le code à l'équipe d'assurance qualité dès que le développement a terminé sa tâche à 80%. Cela signifie que CountDownLatch nous permet de modifier l'implémentation, ce qui peut être utilisé pour attendre un autre thread pour son exécution partielle.
17
V Jo

De la documentation Oracle sur CountDownLatch :

Une aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations effectuées dans d'autres threads.

Un CountDownLatch est initialisé avec un nombre donné. Les méthodes await bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison d'appels de la méthode countDown(), après quoi tous les threads en attente sont libérés et tous les appels ultérieurs de wait sont renvoyés immédiatement. C'est un phénomène ponctuel - le compte ne peut pas être réinitialisé.

CountDownLatch est un outil de synchronisation polyvalent qui peut être utilisé à diverses fins.

CountDownLatch initialisé avec un nombre égal à un sert de simple verrou on/off ou gate: tous les threads appelant attendent en attente à la porte jusqu'à ce qu'il soit ouvert par un thread invoquant countDown ().

Un CountDownLatch initialisé à N peut être utilisé pour faire en sorte qu'un thread attende que N thread ait terminé une action ou qu'une action ait été effectuée N fois.

public void await()
           throws InterruptedException

Amène le thread en cours à attendre que le loquet soit décompté jusqu'à zéro, à moins que le thread ne soit interrompu.

Si le nombre actuel est zéro, cette méthode retourne immédiatement.

public void countDown()

Décrémente le nombre de verrous en libérant tous les threads en attente si le nombre atteint zéro.

Si le nombre actuel est supérieur à zéro, il est décrémenté. Si le nouveau nombre est égal à zéro, tous les threads en attente sont réactivés à des fins de planification.

Explication de votre exemple.

  1. Vous avez défini un nombre de 3 pour la variable latch

    CountDownLatch latch = new CountDownLatch(3);
    
  2. Vous avez passé ce latch partagé au thread de travail: Processor

  3. Trois instances Runnable de Processor ont été soumises à ExecutorServiceexecutor
  4. Le fil principal (App) attend que le compte devienne zéro avec la déclaration ci-dessous

     latch.await();  
    
  5. Le thread Processor reste en sommeil pendant 3 secondes, puis il décrémente la valeur de comptage avec latch.countDown()
  6. La première instance Process changera le nombre de verrous à 2 après son achèvement en raison de latch.countDown().

  7. La deuxième instance Process changera le nombre de verrous à 1 après son achèvement en raison de latch.countDown().

  8. La troisième instance Process changera le nombre de verrous à 0 après son achèvement en raison de latch.countDown().

  9. Compte zéro sur le loquet, le fil principal App sort de await

  10. Le programme d'application imprime cette sortie maintenant: Completed

2
Ravindra babu

Cet exemple de Java Doc m'a aidé à comprendre les concepts clairement:

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

Interprétation visuelle:

enter image description here

Evidemment, CountDownLatch permet à un thread (ici Driver) d'attendre qu'un groupe de threads en cours d'exécution (ici Worker) en ait terminé.

2
Saurav Sahu

Si vous ajoutez un débogage après votre appel à latch.countDown (), cela peut vous aider à mieux comprendre son comportement.

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

La sortie montrera que le compte est décrémenté. Ce "compte" est en réalité le nombre de tâches exécutables (objets Processeur) que vous avez lancées contre countDown () a not été invoqué et est donc bloqué le thread principal lors de son appel à 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
natmat

CountDownLatch vous permet de faire attendre un thread jusqu'à ce que tous les autres threads soient terminés avec leur exécution.

Le pseudo-code peut être:

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

Un bon exemple de ce type d'utilisation est avec Java Simple Serial Connector, qui permet d'accéder aux ports série. En règle générale, vous écrivez quelque chose sur le port et, de façon asynchrone, sur un autre thread, le périphérique répondra sur un SerialPortEventListener. Généralement, vous souhaiterez faire une pause après avoir écrit sur le port pour attendre la réponse. La gestion manuelle des verrous de thread pour ce scénario est extrêmement délicate, mais l’utilisation de Countdownlatch est simple. Avant de penser que vous pouvez le faire autrement, faites attention aux conditions de course auxquelles vous n'aviez jamais pensé!

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
xpusostomos

Comme indiqué dans JavaDoc ( https://docs.Oracle.com/javase/7/docs/api/Java/util/concurrent/CountDownLatch.html ), CountDownLatch est une aide à la synchronisation, introduite dans Java 5. Ici, la synchronisation ne signifie pas restreindre l'accès à une section critique. Mais plutôt séquencer les actions de différents threads. Le type de synchronisation obtenu via CountDownLatch est similaire à celui de Join. Supposons qu'il existe un thread "M" qui doit attendre que les autres threads de travail "T1", "T2" et "T3" terminent leurs tâches. Avant Java 1.5, vous pouvez le faire , M exécutant le code suivant

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

Le code ci-dessus garantit que le thread M reprend son travail après que T1, T2, T3 l'achève. T1, T2, T3 peuvent terminer leur travail dans n'importe quel ordre. La même chose peut être obtenue avec CountDownLatch, où T1, T2, T3 et le thread M partagent le même objet CountDownLatch.
"M" demandes: countDownLatch.await();
où "T1", "T2", "T3" fait countDownLatch.countdown();

Un inconvénient de la méthode de jointure est que M doit connaître T1, T2 et T3. Si un nouveau thread de travail T4 est ajouté ultérieurement, M doit également en être conscient. Cela peut être évité avec CountDownLatch. Après la mise en œuvre, la séquence d'action serait [T1, T2, T3] (l'ordre de T1, T2, T3 pourrait être de toute façon) -> [M]

1
S R Chaitanya
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();
        }
    }
}
0
sumit

Meilleur exemple en temps réel pour countDownLatch expliqué dans ce lien CountDownLatchExample

0
Ashwin Patil