Ce face Executors.newVirtualThreadPerTaskExecutor
Firele virtuale (Java 21) sunt fire de execuție foarte ușoare, gestionate de JVM, nu de sistemul de operare. Poți avea sute de mii odată. Când un fir virtual așteaptă (o pauză, un răspuns de rețea, citirea unui fișier), JVM eliberează firul de sistem pentru alt fir virtual.
newVirtualThreadPerTaskExecutor() nu are un număr fix de fire: fiecare sarcină primește firul ei virtual nou. Nu are rost să refolosești firele virtuale, pentru că sunt ieftine.
Sunt potrivite pentru sarcini care așteaptă mult (cereri HTTP, baze de date). Pentru calcule grele pe procesor nu aduc viteză în plus: acolo rămâi la newFixedThreadPool cu numărul de procesoare.
Ce returnează
Un ExecutorService; folosește-l în try-with-resources ca să aștepți toate sarcinile.
Exemple
Exemplul 1: 10.000 de sarcini care așteaptă câte 200 ms
Cu fire de sistem, 10.000 de pauze de 200 ms ar cere 10.000 de fire. Cu fire virtuale, totul se termină în mai puțin de câteva secunde.
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Main {
public static void main(String[] args) {
AtomicInteger terminate = new AtomicInteger();
Instant start = Instant.now();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
executor.submit(() -> {
Thread.sleep(200);
terminate.incrementAndGet();
return null;
});
}
}
long ms = Duration.between(start, Instant.now()).toMillis();
System.out.println("Sarcini terminate: " + terminate.get());
System.out.println("Sub 5 secunde: " + (ms < 5000));
}
}
Rezultat
Sarcini terminate: 10000
Sub 5 secunde: true
Exemplul 2: Firul este virtual
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 Exception {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Boolean> f = executor.submit(() -> Thread.currentThread().isVirtual());
System.out.println("Sarcina rulează pe un fir virtual: " + f.get());
}
System.out.println("main rulează pe un fir virtual: " + Thread.currentThread().isVirtual());
}
}
Rezultat
Sarcina rulează pe un fir virtual: true
main rulează pe un fir virtual: false
Greșeli frecvente
- Pui un număr maxim de fire virtuale ca la un pool. Nu este nevoie; dacă vrei să limitezi accesul la o resursă (de exemplu 10 conexiuni la baza de date), folosește un
Semaphore. - Folosești fire virtuale pentru calcule matematice lungi. Nu merg mai repede decât firele obișnuite; avantajul lor apare doar la așteptare.
Comenzi înrudite
Executors: Clasa cu metode statice care creează executori: cu număr fix de fire, cu un singur fir, cu fire virtuale sau programate.ExecutorService.close: Oprește executorul și așteaptă să se termine toate sarcinile; permite folosirea executorului în try-with-resources.ExecutorService.submit: Trimite o sarcină executorului și returnează imediat un Future din care iei mai târziu rezultatul.HttpClient.sendAsync: Trimite o cerere HTTP fără să aștepte: returnează imediat un CompletableFuture cu răspunsul.