Ce face Fire de execuție virtuale
Un fir obișnuit (platform thread) corespunde unui fir al sistemului de operare și ocupă memorie multă; câteva mii sunt deja o problemă. Un fir virtual este gestionat de JVM: când așteaptă (o cerere de rețea, un fișier, Thread.sleep), își eliberează firul real, care poate rula alt fir virtual. Așa poți avea sute de mii de fire virtuale.
Se creează cu Thread.ofVirtual().start(...), Thread.startVirtualThread(...) sau, cel mai des, cu Executors.newVirtualThreadPerTaskExecutor(), care pornește un fir virtual nou pentru fiecare sarcină. Executorul se folosește în try-with-resources: close() așteaptă terminarea tuturor sarcinilor.
Firele virtuale ajută la sarcinile care așteaptă mult (servere web, apeluri către API-uri și baze de date), nu la calcule grele, unde numărul de nuclee rămâne limita. Codul rămâne simplu, scris „pas cu pas”, fără callback-uri.
Exemple
Exemplul 1: Un fir virtual
isVirtual() confirmă tipul firului. join() așteaptă terminarea lui înainte ca main să continue.
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t = Thread.ofVirtual().name("descarcare-factura").start(() -> {
Thread curent = Thread.currentThread();
System.out.println("rulez în " + curent.getName() + ", virtual: " + curent.isVirtual());
});
t.join();
System.out.println("main e virtual? " + Thread.currentThread().isVirtual());
}
}
Rezultat
rulez în descarcare-factura, virtual: true
main e virtual? false
Exemplul 2: 10.000 de sarcini care așteaptă
Fiecare sarcină „așteaptă” 1 secundă (ca un apel de rețea). Cu fire virtuale, toate 10.000 rulează în paralel și totul durează puțin peste o secundă, nu 10.000 de secunde. Rezultatele sunt adunate după ce executorul a terminat.
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long start = System.nanoTime();
List<Future<Integer>> rezultate = new ArrayList<>();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 1; i <= 10_000; i++) {
int comanda = i;
rezultate.add(executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return comanda % 7;
}));
}
}
long suma = 0;
for (Future<Integer> f : rezultate) {
suma += f.get();
}
double secunde = (System.nanoTime() - start) / 1e9;
System.out.println("sarcini terminate: " + rezultate.size());
System.out.println("suma rezultatelor: " + suma);
System.out.println("sub 5 secunde? " + (secunde < 5));
}
}
Rezultat
sarcini terminate: 10000
suma rezultatelor: 29998
sub 5 secunde? true
Greșeli frecvente
- Faci un „pool” de fire virtuale (de exemplu
newFixedThreadPoolcu fire virtuale). Firele virtuale sunt ieftine: creează unul nou pentru fiecare sarcină. - Folosești fire virtuale pentru calcule care țin procesorul ocupat și aștepți să meargă mai repede. Câștigul apare doar la sarcinile care așteaptă.
- Uiți să aștepți firele (
joinsau închiderea executorului):mainse termină, iar firele virtuale (care sunt daemon) sunt oprite înainte să-și termine treaba.
Comenzi înrudite
synchronized: synchronized lasă un singur fir de execuție odată într-o metodă sau un bloc, ca datele comune să nu fie corupte.volatile: volatile pe un câmp garantează că fiecare fir vede imediat ultima valoare scrisă; nu face însă operațiile compuse (x++) atomice.try-with-resources: try-with-resources deschide resurse (fișiere, conexiuni) în paranteze și le închide automat la final, chiar dacă apare o excepție.Thread: Un fir de execuție: cod care rulează în paralel cu restul programului. start() îl pornește, join() așteaptă să se termine.Expresii lambda: O lambda este o funcție scurtă, fără nume, scrisă ca parametri -> corp, folosită oriunde se așteaptă o interfață funcțională.