Ce face Thread
Un Thread (fir de execuție) rulează o sarcină în paralel cu firul principal (main). Sarcina este un Runnable, de obicei scris ca lambda: new Thread(() -> ...). Firul nu pornește la creare, ci doar când apelezi start(). Un fir poate fi pornit o singură dată.
join() blochează firul curent până când firul respectiv se termină. Fără join, nu știi când a terminat celălalt fir, iar ordinea mesajelor afișate de fire diferite poate varia de la o rulare la alta. Metoda aruncă InterruptedException (excepție verificată) dacă așteptarea este întreruptă.
Când mai multe fire modifică aceleași date, ai nevoie de sincronizare (synchronized, AtomicInteger, colecții concurente). Pentru multe sarcini scurte, în Java 21+ sunt preferate firele virtuale sau un ExecutorService.
Parametri
| Parametru | Descriere |
|---|---|
sarcina |
Codul de rulat (Runnable). |
nume |
Numele firului (opțional, util la depanare). |
Ce returnează
start și join nu returnează nimic.
Exemple
Exemplul 1: Pornirea și așteptarea unui fir
Mesajul de final apare mereu ultimul, pentru că main așteaptă cu join().
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread fir = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("Descarc fișierul " + i);
}
});
fir.start();
fir.join();
System.out.println("Toate fișierele au fost descărcate.");
}
}
Rezultat
Descarc fișierul 1
Descarc fișierul 2
Descarc fișierul 3
Toate fișierele au fost descărcate.
Exemplul 2: Calcul împărțit pe două fire
Fiecare fir adună o jumătate din numere în propriul element al array-ului, deci nu se încurcă unul pe altul.
public class Main {
public static void main(String[] args) throws InterruptedException {
long[] rezultate = new long[2];
Thread prima = new Thread(() -> {
for (int i = 1; i <= 500_000; i++) {
rezultate[0] += i;
}
}, "prima-jumatate");
Thread aDoua = new Thread(() -> {
for (int i = 500_001; i <= 1_000_000; i++) {
rezultate[1] += i;
}
}, "a-doua-jumatate");
prima.start();
aDoua.start();
prima.join();
aDoua.join();
System.out.println("Suma: " + (rezultate[0] + rezultate[1]));
}
}
Rezultat
Suma: 500000500000
Exemplul 3: Un contor comun, sincronizat
Patru fire cresc același contor de câte 1000 de ori. Cu AtomicInteger rezultatul este mereu 4000.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) throws InterruptedException {
AtomicInteger vizite = new AtomicInteger();
List<Thread> fire = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Thread t = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
vizite.incrementAndGet();
}
});
fire.add(t);
t.start();
}
for (Thread t : fire) {
t.join();
}
System.out.println("Vizite: " + vizite.get());
}
}
Rezultat
Vizite: 4000
Greșeli frecvente
- Apelezi
fir.run()în loc defir.start(): codul rulează în firul curent, nu în paralel. - Apelezi
start()de două ori pe același fir: primeștiIllegalThreadStateException. Creează un fir nou. - Folosești un
intobișnuit ca un contor comun pentru mai multe fire: incrementările se pierd și rezultatul diferă la fiecare rulare.
Comenzi înrudite
Thread.sleep: sleep() oprește firul curent pentru un timp; interrupt() trimite unui fir semnalul să se oprească din așteptare.Thread.currentThread: currentThread() dă firul care rulează codul; getName() îi dă numele, iar isAlive() spune dacă firul încă rulează.Thread.ofVirtual: Creează fire virtuale (Java 21): fire foarte ușoare, gestionate de JVM, din care poți porni mii sau milioane.synchronized: synchronized lasă un singur fir de execuție odată într-o metodă sau un bloc, ca datele comune să nu fie corupte.Fire de execuție virtuale: Firele virtuale sunt fire de execuție foarte ușoare, gestionate de JVM: poți porni zeci de mii de sarcini care așteaptă fără să blochezi sistemul.