Thread.ofVirtual în Java

Creează fire virtuale (Java 21): fire foarte ușoare, gestionate de JVM, din care poți porni mii sau milioane.

Sintaxă
static Thread.Builder.OfVirtual ofVirtual()
static Thread startVirtualThread(Runnable sarcina)

Ce face Thread.ofVirtual

Un fir obișnuit (de platformă) corespunde unui fir al sistemului de operare și consumă destulă memorie, deci poți avea doar câteva mii. Un fir virtual este gestionat de JVM: când așteaptă (rețea, fișiere, sleep), eliberează firul de sistem pe care rula. Astfel poți porni sute de mii de fire virtuale pentru sarcini care stau mult în așteptare, de exemplu cereri către un server.

Thread.startVirtualThread(sarcina) creează și pornește direct un fir virtual. Thread.ofVirtual() returnează un builder cu care poți seta numele (name("cerere-", 0) dă nume numerotate) și apoi crea firul cu start(sarcina) sau unstarted(sarcina). Firele virtuale sunt mereu fire daemon: programul nu le așteaptă la final, așa că folosește join().

Parametri

Parametru Descriere
sarcina Codul de rulat (Runnable).

Ce returnează

ofVirtual: un builder. startVirtualThread: firul pornit.

Exemple

Exemplul 1: Un fir virtual simplu

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.startVirtualThread(() ->
                System.out.println("Rulez într-un fir virtual: " + Thread.currentThread().isVirtual()));
        t.join();
        System.out.println("Firul main este virtual: " + Thread.currentThread().isVirtual());
    }
}

Rezultat

Rulez într-un fir virtual: true
Firul main este virtual: false

Exemplul 2: Fire numite cu builderul

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        String[] orase = {"Iași", "Cluj", "Timișoara"};
        String[] rezultate = new String[orase.length];
        Thread.Builder builder = Thread.ofVirtual().name("meteo-", 1);
        List<Thread> fire = new ArrayList<>();
        for (int i = 0; i < orase.length; i++) {
            int index = i;
            fire.add(builder.start(() ->
                    rezultate[index] = Thread.currentThread().getName() + ": prognoză pentru " + orase[index]));
        }
        for (Thread t : fire) {
            t.join();
        }
        for (String r : rezultate) {
            System.out.println(r);
        }
    }
}

Rezultat

meteo-1: prognoză pentru Iași
meteo-2: prognoză pentru Cluj
meteo-3: prognoză pentru Timișoara

Exemplul 3: 10.000 de sarcini care așteaptă

Fiecare sarcină „așteaptă un răspuns” 100 ms. Cu fire virtuale, toate așteaptă în paralel.

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 terminate = new AtomicInteger();
        List<Thread> fire = new ArrayList<>();
        for (int i = 0; i < 10_000; i++) {
            fire.add(Thread.startVirtualThread(() -> {
                try {
                    Thread.sleep(100);
                    terminate.incrementAndGet();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }));
        }
        for (Thread t : fire) {
            t.join();
        }
        System.out.println("Sarcini terminate: " + terminate.get());
    }
}

Rezultat

Sarcini terminate: 10000

Greșeli frecvente

  • Nu apelezi join(): firele virtuale sunt daemon, iar programul se poate termina înainte ca ele să lucreze.
  • Folosești fire virtuale pentru calcule grele pe procesor: nu sunt mai rapide; avantajul lor apare la sarcini care așteaptă mult.
  • Faci un pool de fire virtuale: nu e nevoie, sunt ieftine. Creează câte un fir nou pentru fiecare sarcină.

Comenzi înrudite

  • 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.
  • Thread: Un fir de execuție: cod care rulează în paralel cu restul programului. start() îl pornește, join() așteaptă să se termine.
  • Thread.currentThread: currentThread() dă firul care rulează codul; getName() îi dă numele, iar isAlive() spune dacă firul încă rulează.
  • Thread.sleep: sleep() oprește firul curent pentru un timp; interrupt() trimite unui fir semnalul să se oprească din așteptare.