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.");
}
}
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.
CountDownLatch
dans Java est un type de synchroniseur qui permet à un Thread
d'attendre un ou plusieurs Thread
s 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.
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....");
}
}
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:
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.
Vous avez défini un nombre de 3 pour la variable latch
CountDownLatch latch = new CountDownLatch(3);
Vous avez passé ce latch
partagé au thread de travail: Processor
Runnable
de Processor
ont été soumises à ExecutorService
executor
Le fil principal (App
) attend que le compte devienne zéro avec la déclaration ci-dessous
latch.await();
Processor
reste en sommeil pendant 3 secondes, puis il décrémente la valeur de comptage avec latch.countDown()
La première instance Process
changera le nombre de verrous à 2 après son achèvement en raison de latch.countDown()
.
La deuxième instance Process
changera le nombre de verrous à 1 après son achèvement en raison de latch.countDown()
.
La troisième instance Process
changera le nombre de verrous à 0 après son achèvement en raison de latch.countDown()
.
Compte zéro sur le loquet, le fil principal App
sort de await
Le programme d'application imprime cette sortie maintenant: Completed
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:
Evidemment, CountDownLatch
permet à un thread (ici Driver
) d'attendre qu'un groupe de threads en cours d'exécution (ici Worker
) en ait terminé.
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]
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
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();
}
}
}
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]
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();
}
}
}
Meilleur exemple en temps réel pour countDownLatch expliqué dans ce lien CountDownLatchExample