CodeGym /Kursy /JAVA 25 SELF /Skalowalność i wydajność Virtual Threads

Skalowalność i wydajność Virtual Threads

JAVA 25 SELF
Poziom 57, Lekcja 2
Dostępny

1. Skalowalność

Dlaczego zwykłe wątki słabo się skalują?

Każdy klasyczny wątek (Thread) — to byt systemu operacyjnego z własnym stosem (zwykle 1–2 MB) i strukturą stanu. Próba utworzenia, powiedzmy, 10 000 zwykłych wątków nierzadko prowadzi do błędu OutOfMemoryError. Dlatego w tradycyjnych serwerach używa się ograniczonych pul wątków.

Wątki wirtualne: magia skalowalności

Wątki wirtualne (Java 21+) — „lekkie” wątki zarządzane przez JVM. Ich stos jest przechowywany na stercie i może dynamicznie rosnąć/kurczyć się. Gdy wątek blokuje się na I/O, JVM „zamraża” go i kontynuuje wykonywanie innych zadań.

Wewnątrz JVM działa niewielka pula „nośników” (carrier threads) — platformowych wątków systemu operacyjnego, na których kolejno wykonywane są wątki wirtualne. To pozwala tworzyć 100 000+ zadań bez grozy pamięciowej. JVM sama planuje, które wątki wirtualne i kiedy wykonywać.

Demonstracja: 100_000 wątków wirtualnych kontra 1_000 platformowych

Przykład: utworzenie 1000 zwykłych wątków

// Próba utworzenia 1000 zwykłych wątków
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    Thread t = new Thread(() -> {
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
    threads.add(t);
    t.start();
}
System.out.println("Utworzono wątków: " + threads.size());

Wynik: Na większości systemów uda się utworzyć 1 000–2 000 wątków; przy większych wartościach zaczną się problemy z pamięcią i spowolnienia.

Przykład: utworzenie 100 000 wątków wirtualnych

// Tworzymy 100_000 wątków wirtualnych
List<Thread> vThreads = new ArrayList<>();
for (int i = 0; i < 100_000; i++) {
    Thread t = Thread.ofVirtual().start(() -> {
        try {
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
    vThreads.add(t);
}
System.out.println("Utworzono wirtualnych wątków: " + vThreads.size());

Wynik: Program bez problemu tworzy 100 000 wątków wirtualnych bez awarii i istotnych spowolnień. Zużycie pamięci jest wielokrotnie mniejsze.

Porównanie wizualne

Typ wątku Maksymalna liczba wątków (w przybliżeniu) Zużycie pamięci Czas uruchomienia
Zwykłe (Thread) 1 000 – 10 000 Wysokie Długo
Wirtualne 100 000 – 1 000 000+ Niskie Natychmiast

Fakt: Wątki wirtualne pozwalają pisać kod „jeden wątek na zadanie” bez złożonych pul i ryzyka przeciążenia systemu.

2. Wydajność: gdzie wątki wirtualne sprawdzają się

Zadania ograniczone przez I/O (I/O‑bound)

Wątki wirtualne świetnie nadają się do zapytań sieciowych, I/O plików i pracy z bazą danych. Gdy operacja się blokuje, wątek wirtualny zwalnia „nośnika”, a JVM wykonuje inne zadania. To zwiększa przepustowość przy dużej liczbie równoczesnych oczekiwań.

Przykład: symulacja 10 000 równoczesnych żądań HTTP

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

HttpClient client = HttpClient.newHttpClient();
List<Thread> threads = new ArrayList<>();

for (int i = 0; i < 10_000; i++) {
    Thread t = Thread.ofVirtual().start(() -> {
        try {
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com"))
                .build();
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            System.out.println("Odpowiedź: " + response.statusCode());
        } catch (Exception e) {
            System.out.println("Błąd: " + e.getMessage());
        }
    });
    threads.add(t);
}

// Czekamy na zakończenie wszystkich wątków
for (Thread t : threads) {
    t.join();
}

Wynik: Wszystkie 10 000 żądań wykonują się równolegle; program nie ulega awarii, a kod pozostaje prosty.

CPU‑bound: wątki wirtualne nie przyspieszają obliczeń

Jeśli zadanie obciąża CPU, wątki wirtualne nie dodadzą szybkości: liczba rdzeni jest stała. Tutaj właściwe są stałe pule równe liczbie rdzeni, aby nie tworzyć zbędnej konkurencji.

// Każde zadanie oblicza sumę dużego zakresu
Runnable cpuTask = () -> {
    long sum = 0;
    for (int i = 0; i < 100_000_000; i++) {
        sum += i;
    }
    System.out.println("Suma: " + sum);
};

// Uruchamiamy 1000 wirtualnych wątków z obliczeniami
for (int i = 0; i < 1000; i++) {
    Thread.ofVirtual().start(cpuTask);
}

Wynik: Wątki będą konkurować o CPU, ale przyspieszenia nie będzie — to nie jest zadanie dla Virtual Threads.

3. Ograniczenia i cechy szczególne

Synchronizacja i pułapki wątków wirtualnych

  • Uwaga na blokady natywne. Użycie synchronized może „przykleić” wątek wirtualny do nośnika, zmniejszając korzyści. Preferuj ReentrantLock, Semaphore i inne prymitywy z java.util.concurrent, zoptymalizowane pod wątki wirtualne.
  • Starsze biblioteki. Niektóre sterowniki JDBC i biblioteki natywne nie są jeszcze zoptymalizowane pod Virtual Threads. Dokładnie testuj operacje blokujące.

Nie do zadań długotrwałych

Virtual Threads są idealne do „krótkich” jednostek pracy: obsługa żądania, jedna operacja i koniec. Milion wiecznie żyjących zadań (np. nieskończone obliczenia) nie przyniesie korzyści — dla nich użyj wątków platformowych.

4. Best practices: gdzie używać wątków wirtualnych

  • Zadania I/O‑bound: wywołania sieciowe, pliki, bazy danych — wszędzie, gdzie wątek często czeka.
  • Serwery WWW: obsługuj każde żądanie HTTP w osobnym wątku wirtualnym.
  • Testy integracyjne: szybko zasymuluj tysiące klientów.
  • Przetwarzanie asynchroniczne: pisz zwykły „blokujący” kod — JVM wykonuje sprytne planowanie.

Nie warto używać:

  • Do zadań stale obciążających CPU.
  • Gdy kluczowa jest zgodność z niskopoziomowymi bibliotekami (nie wszystkie są jeszcze zaadaptowane).

Pod spodem wygodnie użyć executora: Executors.newVirtualThreadPerTaskExecutor() — „jeden wirtualny wątek na zadanie”, bez stałej puli.

5. Monitorowanie i pomiary: jak zobaczyć wątki wirtualne w akcji

JVisualVM i Flight Recorder

JVisualVM pokaże aktywne wątki, ich stany i pamięć; począwszy od Java 21 wątki wirtualne są wyświetlane osobno. Java Flight Recorder (JFR) zapisuje szczegółową „czarną skrzynkę” wykonania, w tym statystyki dla Virtual Threads — wygodne do szukania wąskich gardeł.

Jak sprawdzić liczbę wątków w kodzie

Prosty sposób, aby zobaczyć liczbę wątków w JVM:

System.out.println("Łączna liczba wątków: " + Thread.activeCount());

Policzyć, ile z nich jest wirtualnych:

long vCount = Thread.getAllStackTraces().keySet().stream()
    .filter(Thread::isVirtual)
    .count();
System.out.println("Wirtualnych wątków: " + vCount);

6. Typowe błędy przy pracy z wątkami wirtualnymi

Błąd nr 1: Używanie wątków wirtualnych do ciężkich obliczeń. Uruchomienie milionów wątków wirtualnych z zadaniami CPU‑bound nie przyspieszy procesora. Virtual Threads to nie „turbo” do obliczeń.

Błąd nr 2: Ślepe kopiowanie starych wzorców. Nie twórz stałych pul wątków wirtualnych. Użyj Executors.newVirtualThreadPerTaskExecutor() i pozwól JVM skalować się automatycznie.

Błąd nr 3: Korzystanie z nieobsługiwanych bibliotek. Blokady natywne i biblioteki niezaadaptowane pod Loom mogą powodować zawieszenia i spadki wydajności. Sprawdź zgodność z wyprzedzeniem.

Błąd nr 4: Przedwczesna optymalizacja. Jeśli masz kilka wątków i zwykłą wielowątkowość — nie spiesz się z przenoszeniem wszystkiego na Virtual Threads. To narzędzie jest najlepsze tam, gdzie jest masowe I/O i oczekiwanie.

Błąd nr 5: Ignorowanie monitoringu. Utworzyć milion zadań jest łatwo, ale bez monitoringu i obsługi wyjątków można dostać „ładny benchmark” zamiast niezawodnego systemu. Używaj JVisualVM i JFR.

1
Zadanie
JAVA 25 SELF, poziom 57, lekcja 2
Niedostępne
Monitorowanie populacji wirtualnego miasta 🏙️
Monitorowanie populacji wirtualnego miasta 🏙️
1
Zadanie
JAVA 25 SELF, poziom 57, lekcja 2
Niedostępne
Symulacja obciążenia serwera chmury obliczeniowej ☁️
Symulacja obciążenia serwera chmury obliczeniowej ☁️
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION