Powrót do strony głównej

Wątki w Java: architektura i synchronizacja

Artykuł analizuje budowę wątków w Java od poziomu sprzętowego procesora do JVM. Opisano mechanizmy synchronizacji, problemy Race Condition, Deadlock, Livelock i asynchroniczne CompletableFuture dla efektywnego programowania.

Wielowątkowość Java: od rdzeni CPU do Deadlock
Advertisement 728x90

Architektura wątków i synchronizacja w Javie: od procesora do JVM

Kod Java kompiluje się do bajtkodu, który JVM przekształca w instrukcje maszynowe procesora. Każde rdzenie procesora wykonuje instrukcje sekwencyjnie: w jednym takcie przetwarza prostą operację, taką jak przesunięcie bitu w rejestrze. Rejestry to superszybkie komórki pamięci do przechowywania liczb, adresów i danych.

Współczesne procesory wykorzystują architekturę superskalarową. Hyper-Threading pozwala jednemu sprzętowemu wątkowi wykonywać 2–6 instrukcji na takt, tworząc iluzję wielu logicznych wątków na rdzeniu poprzez szybkie przełączanie kontekstu.

Liczba jednocześnie wykonywanych wątków jest ograniczona liczbą rdzeni. Wątki dzielą pamięć heap, gdzie przechowywane są obiekty, a każdy wątek ma własny stos (stack) dla zmiennych lokalnych i referencji do obiektów.

Google AdInline article slot

Race Condition i struktura pamięci

Race Condition występuje, gdy kilka wątków jednocześnie odczytuje i modyfikuje ten sam obiekt w heap. Wynik jest nieprzewidywalny: zależy od kolejności operacji „odczyt-zmiana-zapis”.

Lokalnie kod często działa poprawnie bez obciążenia, ale w produkcji pod wielowątkowością kolizje ujawniają się nieregularnie. Proces w systemie operacyjnym przydziela wspólną pamięć; wątki w procesie to lekkie jednostki wykonania, konkurujące o zasoby.

Mechanizmy synchronizacji: wait/notify i monitory

Każdy obiekt w Javie ma wbudowany monitor (lock). Blok synchronized przejmuje monitor obiektu, zapewniając:

Google AdInline article slot
  • Mutual Exclusion: tylko jeden wątek wykonuje kod wewnątrz bloku.
  • Visibility: zmiany są widoczne dla innych wątków po wyjściu z bloku (z wsparciem volatile).

Monitor zawiera:

  • Entry Set: kolejka wątków czekających na przejęcie.
  • Wait Set: wątki w stanie oczekiwania.

Metody wait(), notify(), notifyAll() działają tylko wewnątrz bloku synchronized tego samego obiektu, w przeciwnym razie — IllegalMonitorStateException.

  • wait(): przenosi wątek do Wait Set.
  • notify(): budzi losowy wątek (ryzyko lost wakeup).
  • notifyAll(): budzi wszystkie wątki (preferowane).

Statyczne metody synchronized blokują monitor klasy (Class), nie kolidując z metodami instancyjnymi (this).

Google AdInline article slot

Zaawansowane blokady: ReentrantLock i Semaphore

Dla elastycznej kontroli użyj java.util.concurrent.locks.ReentrantLock:

Lock lock = new ReentrantLock();
lock.lock();
try {
    // sekcja krytyczna
} finally {
    lock.unlock();
}

Kluczowe metody:

  • tryLock(): przejęcie bez czekania (false przy niepowodzeniu).
  • tryLock(timeout, unit): czekanie z timeoutem.
  • lockInterruptibly(): przerywalne przejęcie.
  • new ReentrantLock(true): fair locking (FIFO, wolniejsze).

Semaphore ogranicza dostęp:

Semaphore sem = new Semaphore(3); // 3 pozwolenia
sem.acquire(); // przejęcie
sem.release(); // zwolnienie

Problemy wielowątkowości: Deadlock i Livelock

Deadlock: cykliczna zależność blokad. Wątek A trzyma lock1, czeka na lock2; wątek B — odwrotnie. System zawiesza się.

Unikaj:

  • Blokuj w stałej kolejności.
  • Używaj timeoutów.
  • Minimalizuj zagnieżdżone blokady.

Livelock: wątki aktywne, ale nie postępują. Przykład: dwa wątki ustępują sobie zasobów w pętli, obciążając CPU bez postępu.

Synchronność vs asynchroniczność w wątkach

Metoda synchroniczna blokuje wątek na I/O (np. zapytanie do bazy danych): wątek bezczynny do odpowiedzi.

Asynchroniczna używa CompletableFuture — kontenera dla odroczonego wyniku (pending, completed, failed).

Łańcuchy operacji:

  • thenApply(): transformacja wyniku.
  • thenCompose(): kompozycja z innym Future.
  • thenAccept(): konsumpcja bez zwrotu.
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "result")
    .thenApply(s -> s.toUpperCase())
    .thenAccept(System.out::println);

To zwalnia wątek do innych zadań, zwiększając przepustowość w środowisku wielowątkowym.

Co ważne

  • Każde jądro wykonuje jeden sprzętowy wątek; Hyper-Threading emuluje wielość.
  • Race Condition od współdzielonego heap; synchronizacja gwarantuje mutual exclusion i visibility.
  • notifyAll() preferowane przed notify() dla uniknięcia lost wakeup.
  • ReentrantLock rozszerza synchronized: timeouty, przerwania, fairness.
  • CompletableFuture buduje asynchroniczne potoki bez blokowania wątków.

— Editorial Team

Advertisement 728x90

Czytaj dalej