☕ Java Aktualizacja: 30.01.2026

Virtual Threads (Project Loom) w praktyce produkcyjnej

Virtual Threads (Project Loom) w Java 21 pozwalają skalować aplikacje I/O-bound bez rewolucji w kodzie — przykłady, pułapki i checklisty wdrożeniowe.

Czas czytania: ok. 6–8 minut Frazy kluczowe: Virtual Threads Java, Project Loom, Java 21 concurrency, skalowanie aplikacji Java

Dokumentacja źródłowa

Virtual Threads (Project Loom) to jedna z największych zmian w modelu współbieżności Javy od lat. Wprowadzone oficjalnie jako funkcja stabilna w Java 21 (LTS), pozwalają pisać kod w stylu klasycznego programowania blokującego, a jednocześnie skalować aplikację do setek tysięcy równoległych zadań.

W tym artykule pokazujemy:

  • czym są Virtual Threads,
  • kiedy mają sens w produkcji,
  • proste przykłady kodu,
  • typowe pułapki,
  • linki do dokumentacji do dalszej nauki.

Dlaczego Virtual Threads są przełomem?

Tradycyjne wątki systemowe (platform threads):

  • są ciężkie (każdy to osobny wątek OS),
  • ograniczone liczbą zasobów systemowych,
  • drogie w tworzeniu i przełączaniu kontekstu.

Virtual Threads:

  • są zarządzane przez JVM, a nie bezpośrednio przez system operacyjny,
  • są lekkie (kilkadziesiąt tysięcy to norma),
  • świetnie nadają się do zadań I/O (HTTP, DB, kolejki, pliki).

Ciekawostka

W testach laboratoryjnych JVM potrafi obsłużyć miliony virtual threads, o ile nie blokują się one na zasobach systemowych w niekontrolowany sposób.

Najprostszy przykład: Platform Thread vs Virtual Thread

Klasyczny wątek (Java 8+)

Thread thread = new Thread(() -> {
    System.out.println("Hello from platform thread");
});
thread.start();

Virtual Thread (Java 21+)

Thread.startVirtualThread(() -> {
    System.out.println("Hello from virtual thread");
});

Lub z użyciem ExecutorService:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        System.out.println("Task on virtual thread");
    });
}

Kod wygląda niemal identycznie, ale:

  • platform thread = ciężki zasób systemowy,
  • virtual thread = lekki byt zarządzany przez JVM.

Virtual Threads a aplikacje webowe (Spring / backend)

Największy zysk widać w aplikacjach typu:

  • REST API,
  • mikroserwisy,
  • systemy integracyjne (HTTP, DB, messaging),
  • aplikacje o dużej liczbie jednoczesnych requestów.

Przykład prostego kontrolera, który blokuje się na I/O:

@GetMapping("/data")
public String getData() throws InterruptedException {
    Thread.sleep(1000); // symulacja I/O (np. DB, HTTP)
    return "OK";
}

Na platform threads:

  • każdy request = osobny wątek OS,
  • szybki limit przy dużym ruchu.

Na virtual threads:

  • każdy request = virtual thread,
  • JVM sama mapuje je na pulę wątków systemowych.

Efekt: znacznie lepsza skalowalność przy tym samym kodzie.

Co działa świetnie z Virtual Threads?

Idealne przypadki:

  • aplikacje I/O-bound (sieć, bazy danych, pliki),
  • klasyczny kod blokujący (sleep, JDBC, REST clients),
  • systemy z dużą liczbą jednoczesnych użytkowników.

Działa bez przepisywania aplikacji na reaktywną:

  • nie trzeba przechodzić na WebFlux czy Reactor,
  • zachowujemy prosty, synchroniczny styl kodu.

Czego unikać (ważne w produkcji)

1) Operacje CPU-bound

Virtual Threads nie przyspieszą:

  • ciężkich obliczeń,
  • algorytmów matematycznych,
  • przetwarzania danych w pętli CPU.

Tu nadal potrzebujesz klasycznej kontroli puli wątków.

2) Blokowanie na niekompatybilnych bibliotekach

Niektóre stare biblioteki:

  • używają natywnego kodu,
  • blokują wątki w sposób, którego JVM nie potrafi „odczepić”.

Może to zniwelować korzyści z Virtual Threads.

3) Synchronizacja na dużą skalę

Kod w stylu:

synchronized (lock) {
    Thread.sleep(1000);
}

to antywzorzec przy tysiącach virtual threads – blokujesz współdzielony zasób.

Praktyczny przykład: 100 000 zadań

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 100_000; i++) {
        executor.submit(() -> {
            Thread.sleep(1000);
            return null;
        });
    }
}

Na platform threads: aplikacja szybko się „zadławi”. Na virtual threads: JVM spokojnie obsłuży taką liczbę zadań (o ile masz pamięć i nie blokujesz krytycznych sekcji).

Uwaga: ten sam kod z newFixedThreadPool(200) zachowa się zupełnie inaczej – zadania będą czekać w kolejce.

Czy Virtual Threads zastąpią programowanie reaktywne?

Nie do końca, ale zmieniają równowagę sił.

Reaktywność (Reactor, WebFlux):

  • nadal świetna przy strumieniach danych,
  • wymaga innego modelu myślenia.

Virtual Threads:

  • pozwalają pisać prosty kod synchroniczny,
  • osiągać podobną skalowalność w wielu przypadkach,
  • są łatwiejsze do wdrożenia w istniejących systemach (legacy).

Dla wielu zespołów to ogromne uproszczenie architektury.

Checklista produkcyjna (przed wdrożeniem)

  • [ ] Java 21 LTS
  • [ ] Testy wydajnościowe (porównanie platform vs virtual threads)
  • [ ] Sprawdzenie bibliotek (JDBC driver, HTTP client, SDK chmurowe)
  • [ ] Monitoring: liczba wątków, heap, czasy odpowiedzi
  • [ ] Brak krytycznych sekcji z synchronized na długie operacje
  • [ ] Fallback (łatwa możliwość powrotu do platform threads)

Oficjalne źródła i dokumentacja

Podsumowanie

Virtual Threads to realna zmiana paradygmatu:

  • pozwalają skalować klasyczny kod blokujący,
  • upraszczają architekturę backendów,
  • redukują potrzebę skomplikowanych rozwiązań reaktywnych,
  • są szczególnie wartościowe w systemach I/O-bound.

W praktyce produkcyjnej sprawdzają się najlepiej tam, gdzie:

  • mamy dużo jednoczesnych requestów,
  • dużo czekania na I/O,
  • chcemy zachować czytelny, synchroniczny kod.

Jak wykorzystujemy Virtual Threads w R&D

W projektach R&D stosujemy Virtual Threads:

  • w gatewayach API,
  • w systemach integracyjnych,
  • w batch processingu opartym o I/O,
  • jako eksperymentalny replacement dla klasycznych thread pooli.

Każde wdrożenie poprzedzamy:

  • testami obciążeniowymi,
  • analizą bibliotek,
  • monitoringiem po wdrożeniu.

Polecany artykuł

Chatbot offline -- czy to możliwe?

Przejdź do artykułu →