Ce face CountDownLatch
CountDownLatch pornește de la un număr N. Fiecare apel countDown() scade numărul cu 1. await() blochează firul care îl apelează până când numărul ajunge la 0. Este ca o ușă care se deschide după ce au sosit toți cei N participanți.
Folosiri obișnuite: firul principal așteaptă ca N fire de lucru să termine (new CountDownLatch(N)), sau N fire așteaptă un semnal de start comun (new CountDownLatch(1)).
Un CountDownLatch nu se poate reseta: după ce a ajuns la 0, await() revine imediat. await(timp, unitate) returnează false dacă timpul expiră înainte de 0. countDown() se pune de obicei în finally, ca să fie apelat și când sarcina eșuează.
Parametri
| Parametru | Descriere |
|---|---|
numar |
De câte ori trebuie apelat countDown(). |
timp, unitate |
Pentru await cu limită de timp. |
Ce returnează
await() nu returnează nimic; await(timp, unitate) returnează true dacă numărul a ajuns la 0 la timp.
Exemple
Exemplul 1: Așteptarea a trei fire de lucru
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
String[] fisiere = {"ianuarie.csv", "februarie.csv", "martie.csv"};
int[] randuri = new int[fisiere.length];
CountDownLatch gata = new CountDownLatch(fisiere.length);
for (int i = 0; i < fisiere.length; i++) {
int poz = i;
new Thread(() -> {
try {
randuri[poz] = fisiere[poz].length() * 10;
} finally {
gata.countDown();
}
}).start();
}
gata.await();
System.out.println("Toate fișierele au fost procesate (count = " + gata.getCount() + ")");
for (int i = 0; i < fisiere.length; i++) {
System.out.println(fisiere[i] + ": " + randuri[i] + " rânduri");
}
}
}
Rezultat
Toate fișierele au fost procesate (count = 0)
ianuarie.csv: 120 rânduri
februarie.csv: 130 rânduri
martie.csv: 100 rânduri
Exemplul 2: Semnal de start comun
Alergătorii așteaptă toți același semnal. Ordinea în care pornesc diferă, deci afișăm doar câți au pornit.
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch start = new CountDownLatch(1);
CountDownLatch auPornit = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
new Thread(() -> {
try {
start.await();
auPornit.countDown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
Thread.sleep(100);
System.out.println("Înainte de start au pornit: " + (4 - auPornit.getCount()));
start.countDown();
auPornit.await();
System.out.println("După start au pornit: " + (4 - auPornit.getCount()));
}
}
Rezultat
Înainte de start au pornit: 0
După start au pornit: 4
Exemplul 3: await cu limită de timp
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException {
CountDownLatch confirmari = new CountDownLatch(3);
confirmari.countDown();
confirmari.countDown();
boolean toate = confirmari.await(200, TimeUnit.MILLISECONDS);
System.out.println("Toate cele 3 confirmări au sosit? " + toate + " (lipsesc " + confirmari.getCount() + ")");
}
}
Rezultat
Toate cele 3 confirmări au sosit? false (lipsesc 1)
Greșeli frecvente
- Apelezi
countDown()doar pe calea fericită. Dacă o sarcină aruncă o excepție, numărul nu ajunge la 0 șiawait()așteaptă la nesfârșit; punecountDown()înfinally. - Încerci să refolosești un
CountDownLatchdupă ce a ajuns la 0. Nu se resetează; creează unul nou sau foloseșteCyclicBarrier/Phaser.
Comenzi înrudite
ExecutorService: Un grup de fire de execuție care primește sarcini și le rulează; se oprește cu shutdown() și se așteaptă cu awaitTermination().ReentrantLock: Un lacăt: lock() lasă un singur fir să intre în zona protejată, unlock() îl eliberează; unlock se pune mereu în finally.TimeUnit: Enumerarea unităților de timp (SECONDS, MINUTES…) folosită la așteptări; face conversii și pauze ușor de citit.AtomicInteger: Un int pe care mai multe fire îl pot modifica sigur, fără synchronized: get(), set(), addAndGet() și altele.