CountDownLatch în Java

Un numărător pentru așteptare: await() blochează până când alte fire apelează countDown() de N ori.

Sintaxă
import java.util.concurrent.CountDownLatch;

CountDownLatch semnal = new CountDownLatch(numar);
void countDown()
void await() throws InterruptedException
boolean await(long timp, TimeUnit unitate) throws InterruptedException
long getCount()

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 și await() așteaptă la nesfârșit; pune countDown() în finally.
  • Încerci să refolosești un CountDownLatch după ce a ajuns la 0. Nu se resetează; creează unul nou sau folosește CyclicBarrier / 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.