0Pricing
Java Academy · Lekcja

Pułapki: przypinanie i ThreadLocal

Unikanie blokowania wątków nośnych

Pułapki: przypinanie i ThreadLocal to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Co może pójść nie tak

Wątki wirtualne są potężnym narzędziem, ale dwie pułapki mogą po cichu ograniczyć ich skalowalność: przypinanie oraz nadmierne używanie ThreadLocal.

W tej lekcji wyjaśnimy oba problemy i sposoby ich unikania.

Czym jest przypinanie

Przypinanie występuje, gdy wątek wirtualny nie może odmontować się od wątku nośnego podczas blokowania. Wątek systemu operacyjnego pełniący funkcję nośnego pozostaje zajęty, co niweczy korzyść w postaci skalowalności.

Gdy zbyt wiele wątków nośnych jest przypiętych, przepustowość gwałtownie spada.

Przyczyna: blokowanie w synchronized

Klasyczną przyczyną jest blokowanie wewnątrz bloku lub metody synchronized. Gdy wątek wirtualny posiada monitor i następnie się blokuje, pozostaje przypięty do swojego wątku nośnego.

Uwaga: w JDK 24 i nowszych (JEP 491) to ograniczenie zostało w dużej mierze usunięte, ale w Java 21 stanowi rzeczywisty problem.

Przykład przypinania, którego należy unikać

Ten wzorzec może powodować przypinanie w Java 21: blokowanie podczas posiadania monitora. Kod nadal działa poprawnie, ale nie skaluje się.

public class Main {
    static final Object lock = new Object();

    static void risky() {
        synchronized (lock) {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::risky);
        t.join();
        System.out.println("Done (but this blocked inside synchronized)");
    }
}

Rozwiązanie: ReentrantLock

Gdy sekcja krytyczna może się blokować, należy zastąpić synchronized obiektem ReentrantLock. Interfejsy blokad są przyjazne dla wątków wirtualnych i umożliwiają odmontowanie.

import java.util.concurrent.locks.ReentrantLock;

public class Main {
    static final ReentrantLock lock = new ReentrantLock();

    static void safe() {
        lock.lock();
        try {
            try { Thread.sleep(10); } catch (InterruptedException e) {}
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(Main::safe);
        t.join();
        System.out.println("Done without pinning");
    }
}

Diagnozowanie przypinania

Można polecić JVM wypisywanie śladu stosu za każdym razem, gdy wystąpi przypinanie, uruchamiając program z opcją:

  • -Djdk.tracePinnedThreads=full w celu uzyskania pełnych śladów
  • -Djdk.tracePinnedThreads=short w celu uzyskania wersji jednolinijkowej

Pomaga to zlokalizować problematyczne sekcje synchronized.

ThreadLocals stają się kosztowne

ThreadLocal przechowuje stan przypisany do wątku. W przypadku kilku wątków platformowych nie stanowi to problemu. Jednak przy milionach wątków wirtualnych, z których każdy przechowuje własną kopię, zużycie pamięci gwałtownie rośnie.

Należy przeprowadzić audyt używanych bibliotek: pamięć podręczna, formatowanie i obiekty kontekstu często ukrywają użycie ThreadLocal.

ThreadLocal nadal działa

ThreadLocal nie jest zabroniony — należy tylko używać go oszczędnie. Każdy wątek wirtualny otrzymuje własną wartość, zgodnie z oczekiwaniami.

public class Main {
    static final ThreadLocal<String> CONTEXT = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        Thread t = Thread.ofVirtual().start(() -> {
            CONTEXT.set("request-42");
            System.out.println("Context: " + CONTEXT.get());
            CONTEXT.remove();
        });
        t.join();
    }
}

Preferuj ScopedValue

Java wprowadziła ScopedValue jako lżejszą alternatywę do współdzielenia niezmiennych danych o ograniczonym czasie życia. Pozwala uniknąć kosztu zmiennego magazynu przypisanego do każdego wątku, charakterystycznego dla ThreadLocal, i pasuje do modelu uporządkowanej współbieżności.

Jeśli trzeba jedynie przekazać kontekst tylko do odczytu w dół łańcucha wywołań, warto użyć właśnie tego rozwiązania.

Nie umieszczaj ich w puli

Kolejną subtelną pułapką jest traktowanie wątków wirtualnych jak deficytowego zasobu i umieszczanie ich w puli. Przywraca to rywalizację oraz wyciek danych ThreadLocal między zadaniami.

Zawsze należy tworzyć jeden wątek wirtualny dla każdego zadania i pozwolić mu się zakończyć.

Lista kontrolna skalowania

Przed wdrożeniem wątków wirtualnych na produkcji:

  • Zastąp blokujące synchronized obiektem ReentrantLock
  • Włącz jdk.tracePinnedThreads podczas testów
  • Ogranicz użycie ThreadLocal; preferuj ScopedValue
  • Nigdy nie umieszczaj wątków wirtualnych w puli

Szybkie sprawdzenie

Wskaż bezpieczny zamiennik blokującego bloku synchronized.

Podsumowanie

Poznali Państwo najważniejsze pułapki:

  • Przypinanie: blokowanie wewnątrz synchronized zajmuje wątek nośny; rozwiązaniem jest ReentrantLock
  • Diagnozowanie za pomocą -Djdk.tracePinnedThreads
  • Zużycie zasobów przez ThreadLocal rośnie wraz z liczbą wątków; należy preferować ScopedValue
  • Nigdy nie umieszczaj wątków wirtualnych w puli

To kończy kurs dotyczący wątków wirtualnych.

Często zadawane pytania

Czy lekcja „Pułapki: przypinanie i ThreadLocal” jest bezpłatna?

Tak — pełny tekst „Pułapki: przypinanie i ThreadLocal” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Pułapki: przypinanie i ThreadLocal”?

Unikanie blokowania wątków nośnych Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.

Ile czasu zajmuje lekcja „Pułapki: przypinanie i ThreadLocal”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Czym są wirtualne wątki
  2. Tworzenie wirtualnych wątków
  3. Wątki platformowe a wirtualne
  4. Pułapki: przypinanie i ThreadLocal
← Powrót do Java Academy