Thread în Java

Un fir de execuție: cod care rulează în paralel cu restul programului. start() îl pornește, join() așteaptă să se termine.

Sintaxă
public class Thread implements Runnable
new Thread(Runnable sarcina)
new Thread(Runnable sarcina, String nume)
void start()
void join() throws InterruptedException

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 de fir.start(): codul rulează în firul curent, nu în paralel.
  • Apelezi start() de două ori pe același fir: primești IllegalThreadStateException. Creează un fir nou.
  • Folosești un int obiș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.