Comment CountDownLatch est-il utilisé dans le multithreading Java?


184

Quelqu'un peut-il m'aider à comprendre ce que Java CountDownLatch et quand 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 à la fois et chaque thread appellera CountDownLatch après 3000 ms. Le compte à rebours diminuera donc un par un. Une fois que le verrou est à zéro, le programme imprime "Terminé". 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.");
    }

}


9
Je viens d'utiliser votre exemple de code de question pour un lot de services parallèles Android et cela a fonctionné comme un charme. Merci beaucoup!
Roisgoen

Obtenu ici de cette vidéo de 2012, qui montre une ressemblance remarquable avec l'exemple montré ici. Pour toute personne intéressée, cela fait partie d'une série de tutoriels Java multi-threading d'un type nommé John. J'aime John. Hautement recommandé.
Elia Grady

Réponses:


194

Oui, vous avez bien compris. CountDownLatchfonctionne selon le principe du verrouillage, le filetage principal attendra que la porte soit ouverte. Un thread attend n threads, spécifié lors de la création du CountDownLatch.

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

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

Éditer:

Utilisation CountDownLatch lorsqu'un thread (comme le thread principal) doit attendre qu'un ou plusieurs threads se terminent, avant de pouvoir continuer le traitement.

Un exemple classique d'utilisation CountDownLatch en Java est une application Java principale côté serveur 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 tant que tous les services n'ont pas démarré avec succès.

La question de PS OP a un exemple assez simple, je n'en ai donc pas inclus.


1
Merci de votre réponse. Pourriez-vous me donner un exemple où appliquer le verrou CountDown?
amal

11
un tutoriel sur la façon d'utiliser CountDownLatch est ici howtodoinjava.com/2013/07/18
...

1
@NikolaB Mais dans cet exemple donné, nous pouvons obtenir le même résultat en utilisant la méthode join n'est-ce pas?
Vikas Verma

3
Je considérerais la non-réutilisabilité comme un avantage: vous êtes sûr que personne ne peut le réinitialiser ou augmenter le nombre.
ataulm

3
Belle explication. Mais je serais légèrement en désaccord sur ce point One thread waits for n number of threads specified while creating CountDownLatch in Java. Si vous avez besoin d'un tel mécanisme, il est prudent de l'utiliser CyclicBarrier. La différence conceptuelle fondamentale entre ces deux, comme indiqué dans Java concurrency in Practiceest: Latches are for waiting for events; barriers are for waiting for other threads. cyclicBarrier.await()passe dans un état de blocage.
Rahul Dev Mishra

43

CountDownLatchen Java est un type de synchroniseur qui permet Thread d'attendre un ou plusieursThread s avant de commencer le traitement.

CountDownLatchfonctionne sur le principe du verrou, le fil attendra que la porte soit ouverte. Un thread attend le nnombre de threads spécifié lors de la créationCountDownLatch .

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

Ici, nous mettons le compteur à 3.

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

Ici, le compte est décrémenté de CountDownLatch.countDown() méthode.

Le Threadqui appelle leawait() méthode attendra que le décompte initial atteigne zéro.

Pour rendre le compte nul, les autres threads doivent appeler la countDown()méthode. Une fois que le compte est devenu zéro, le thread qui a invoqué leawait() méthode reprend (démarre son exécution).

L'inconvénient de CountDownLatchest qu'il n'est pas réutilisable: une fois que le compte devient nul, il n'est plus utilisable.


utilisons-nous le new CountDownLatch(3)comme nous avons 3 threads du newFixedThreadPool défini?
Chaklader Asfak Arefe

"avant qu'il ne commence le traitement" ne devrait-il pas être "avant qu'il continue le traitement"?
Maria Ines Parnisari

@Arefe Oui, c'est le nombre de threads passant par votre bloc de code
Vishal Akkalkote

23

NikolaB l'a très bien expliqué, cependant un exemple serait utile à comprendre, 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....");



     }
  }

22

Il est utilisé lorsque nous voulons attendre plus d'un thread pour terminer sa tâche. C'est similaire à rejoindre des threads.

Où nous pouvons utiliser CountDownLatch

Considérez un scénario où nous avons une exigence où 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 achèvent partiellement leur tâche.

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

Imaginez un scénario dans lequel le responsable a divisé les modules entre les équipes de développement (A et B) et il souhaite les attribuer à l'équipe d'assurance qualité pour des tests uniquement 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 confiée à l'équipe de développement devB

Tâche confié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 QA

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

Ici Attendre les () méthode attend drapeau CountDownLatch à devenir 0 et Countdown () méthode décrémente drapeau CountDownLatch par 1.

Limitation de JOIN: L' exemple ci-dessus peut également être réalisé 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 où le gestionnaire 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 qui peut être utilisée pour attendre un autre thread pour leur exécution partielle.

3

CoundDownLatch vous permet de faire attendre un thread jusqu'à ce que tous les autres threads aient terminé 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

Vous voudrez peut-être supprimer toute votre description du bloc de code
Paul Lo

Meilleur commentaire cependant. J'aime ces commentaires «directs» au lieu d'explications théoriques.
renatoaraujoc

2

Un bon exemple d'utilisation de quelque chose comme celui-ci est avec Java Simple Serial Connector, qui accède aux ports série. En règle générale, vous allez écrire quelque chose sur le port, et de manière asyncronale, sur un autre thread, le périphérique répondra sur un SerialPortEventListener. En règle générale, 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 facile. Avant de penser que vous pouvez le faire autrement, faites attention aux conditions de course auxquelles vous n'avez 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

Si vous ajoutez du 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 affichera le décompte en cours de décrémentation. Ce «compte» est en fait le nombre de tâches exécutables (objets Processeur) que vous avez démarrées contre lesquelles countDown () n'a pas é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

À partir de la documentation d'Oracle sur CountDownLatch :

Aide à la synchronisation qui permet à un ou plusieurs threads d'attendre la fin d'un ensemble d'opérations en cours d'exécution dans d'autres threads.

A CountDownLatchest initialisé avec un décompte donné. Les awaitméthodes se bloquent jusqu'à ce que le nombre actuel atteigne zéro en raison des appels de la countDown()méthode, après quoi tous les threads en attente sont libérés et toutes les invocations ultérieures de wait sont immédiatement renvoyées. Il s'agit d'un phénomène ponctuel - le décompte ne peut pas être réinitialisé.

Un CountDownLatch est un outil de synchronisation polyvalent et peut être utilisé à plusieurs fins.

Un CountDownLatchinitialisé avec un compte de un sert de simple verrou marche / arrêt, ou porte: tous les threads invoquant attendent attendre à la porte jusqu'à ce qu'il soit ouvert par un thread invoquant countDown ().

Un CountDownLatchinitialisé à N peut être utilisé pour faire attendre un thread jusqu'à ce que N threads aient terminé une action ou qu'une action ait été effectuée N fois.

public void await()
           throws InterruptedException

Provoque l'attente du thread actuel jusqu'à ce que le verrou ait compté à zéro, sauf si le thread est interrompu.

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

public void countDown()

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

Si le décompte 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 de thread.

Explication de votre exemple.

  1. Vous avez défini le compte à 3 pour la latchvariable

    CountDownLatch latch = new CountDownLatch(3);
  2. Vous avez transmis ce partage latchau fil de discussion Worker:Processor

  3. Trois Runnableinstances de Processoront été soumises àExecutorService executor
  4. Le thread principal ( App) attend que le nombre devienne zéro avec l'instruction ci-dessous

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

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

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

  9. Le comptage nul sur le verrou provoque le filetage principal App sortir leawait

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


2

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

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:

entrez la description de l'image ici

Evidemment, CountDownLatchpermet à un thread (ici Driver) d'attendre jusqu'à ce qu'un tas de threads en cours (ici Worker) aient terminé leur exécution.


1

Comme mentionné 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 fonctionne pas signifie 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 d'autres threads de travail "T1", "T2", "T3" terminent ses tâches Avant Java 1.5, la façon dont cela peut être fait est, 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 ait terminé son travail. T1, T2, T3 peuvent terminer leurs travaux dans n'importe quel ordre. La même chose peut être obtenue via CountDownLatch, où T1, T2, T3 et thread M partagent le même objet CountDownLatch.
Requêtes "M": countDownLatch.await();
où comme "T1", "T2", "T3" fait countDownLatch.countdown();

Un inconvénient de la méthode de jointure est que M doit connaître T1, T2, 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]



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();
        }
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.