Executors.newVirtualThreadPerTaskExecutor în Java

Creează un executor care pornește un fir virtual nou pentru fiecare sarcină; potrivit pentru mii de sarcini care așteaptă.

Sintaxă
static ExecutorService newVirtualThreadPerTaskExecutor()

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.