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=fullw celu uzyskania pełnych śladów-Djdk.tracePinnedThreads=shortw 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
synchronizedobiektemReentrantLock - Włącz
jdk.tracePinnedThreadspodczas testów - Ogranicz użycie
ThreadLocal; preferujScopedValue - 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
synchronizedzajmuje wątek nośny; rozwiązaniem jestReentrantLock - 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
- Czym są wirtualne wątki
- Tworzenie wirtualnych wątków
- Wątki platformowe a wirtualne
- Pułapki: przypinanie i ThreadLocal