CodeGym /Kursy /JAVA 25 SELF /Stany i cykl życia wątku

Stany i cykl życia wątku

JAVA 25 SELF
Poziom 51 , Lekcja 2
Dostępny

1. Podstawowe stany wątku w Javie

W Javie wątek to nie tylko „uruchomiony” lub „zatrzymany”. Ma pełny cykl życia i na każdym etapie zachowuje się inaczej. Zrozumienie tych etapów — to klucz do pisania stabilnych aplikacji wielowątkowych oraz do debugowania dziwnych „zawieszeń” i „nieoczekiwanych zakończeń”.

Jakie są stany?

Java definiuje następujące podstawowe stany wątku (są wymienione w wyliczeniu Thread.State):

Stan Opis
NEW
Wątek został utworzony, ale jeszcze nie uruchomiony (new Thread(...), lecz start() nie został wywołany)
RUNNABLE
Wątek jest gotowy do wykonania lub wykonuje się w tej chwili
BLOCKED
Wątek oczekuje na zwolnienie monitora (zablokowany na bloku synchronized)
WAITING
Wątek czeka, aż inny wątek go „obudzi” (np. przez Object.wait())
TIMED_WAITING
Wątek czeka z limitem czasu (np. Thread.sleep(1000), wait(1000), join(1000))
TERMINATED
Wątek zakończył działanie

Ciekawostka:
W starszych książkach i artykułach możesz spotkać inne nazwy lub nieco inne schematy. Jednak od Java 5+ te stany uważa się za standard.

Wizualny schemat cyklu życia wątku

stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> BLOCKED: próba wejścia do synchronized, ale monitor jest zajęty
    BLOCKED --> RUNNABLE: monitor zwolniony
    RUNNABLE --> WAITING: wait(), join()
    RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
    WAITING --> RUNNABLE: notify()/notifyAll(), join() się zakończył
    TIMED_WAITING --> RUNNABLE: upłynął timeout / notify()/notifyAll()
    RUNNABLE --> TERMINATED: run() się zakończył
    WAITING --> TERMINATED: run() się zakończył (rzadko)
    TIMED_WAITING --> TERMINATED: run() się zakończył (rzadko)

2. Metody zarządzania wątkiem

Sen: Thread.sleep(long millis)

Czasem wątek musi „pospać”, aby nie przeszkadzać innym lub poczekać na zdarzenie. Metoda Thread.sleep(ms) przenosi wątek w stan

TIMED_WAITING
na podaną liczbę milisekund.

System.out.println("Wątek zasypia na 2 sekundy...");
Thread.sleep(2000); // Usypiamy na 2 sekundy
System.out.println("Wątek się obudził!");
  • Po zakończeniu snu wątek wraca do stanu
    RUNNABLE
    (gotowy do pracy).
  • Jeśli wątek zostanie przerwany podczas snu, zostanie zgłoszony InterruptedException.

Oczekiwanie na zakończenie innego wątku: join()

Czasem nie wystarczy uruchomić wątek — trzeba poczekać, aż zakończy pracę. Do tego w Javie służy metoda join():

Thread t = new Thread(() -> {
    System.out.println("Pracuję...");
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("Gotowe!");
});
t.start();
System.out.println("Czekam na zakończenie wątku t...");
t.join(); // Bieżący wątek (np. main) czeka na t
System.out.println("Wątek t się zakończył!");

Tutaj wątek główny zaczyna czekać, gdy tylko wywołana zostaje join(). Przez cały ten czas jest w stanie

WAITING
, dopóki wątek t nie zakończy działania. Jest też wariant z limitem czasu — join(long millis). W takim przypadku wątek czeka ograniczony czas, a jego stan będzie
TIMED_WAITING
.

Przerwanie wątku: interrupt()

Zdarza się, że trzeba grzecznie poprosić wątek o zakończenie pracy, np. gdy użytkownik kliknął „Anuluj”. Do tego służy metoda interrupt():

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // Pracujemy...
    }
    System.out.println("Wątek zakończony przez przerwanie!");
});
t.start();
// ... po pewnym czasie:
t.interrupt(); // Sygnał dla wątku: "czas się zatrzymać"

Ważne jest, że wywołanie interrupt() nie zabija wątku natychmiast. Ustawia jedynie specjalną flagę. Sam wątek powinien od czasu do czasu sprawdzać tę flagę przez isInterrupted() i zakończyć się samodzielnie. Jeśli wątek znajduje się wtedy w stanie snu (sleep()) lub oczekiwania (wait()), to nie tylko zobaczy flagę, ale od razu otrzyma wyjątek InterruptedException. Tak właśnie w Javie wygląda „poprawny” sposób zatrzymywania wątków: program nie przerywa ich siłą, lecz informuje, że pora się zakończyć.

3. Przykłady przejść między stanami

Przyjrzyjmy się na przykładach, jak wątek „podróżuje” po swoich stanach.

Przykład 1:
NEW
RUNNABLE
TERMINATED

Thread t = new Thread(() -> System.out.println("Cześć!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (lub TERMINATED, jeśli wątek jest bardzo szybki)
t.join();
System.out.println(t.getState()); // TERMINATED

Przykład 2:
RUNNABLE
TIMED_WAITING
RUNNABLE
TERMINATED

Thread t = new Thread(() -> {
    try {
        System.out.println("Zasypiam...");
        Thread.sleep(1000); // TIMED_WAITING
        System.out.println("Budzę się!");
    } catch (InterruptedException e) {
        System.out.println("Wątek przerwany!");
    }
});
t.start();

Przykład 3:
RUNNABLE
WAITING
z join()

Thread t1 = new Thread(() -> {
    try { Thread.sleep(500); } catch (InterruptedException ignored) {}
    System.out.println("t1 zakończony");
});
Thread t2 = new Thread(() -> {
    try {
        t1.join(); // t2 czeka na t1, jest w WAITING
        System.out.println("t2 doczekał się t1");
    } catch (InterruptedException ignored) {}
});
t1.start();
t2.start();

Przykład 4:
BLOCKED

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        System.out.println("t1 wyszedł z bloku synchronized");
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 wszedł do synchronized");
    }
});
t1.start();
Thread.sleep(100); // Dajmy t1 przejąć lock
t2.start();
Thread.sleep(100); // Dajmy t2 spróbować wejść do synchronized
System.out.println("Stan t2: " + t2.getState()); // BLOCKED

4. Jak poznać stan wątku? Metody isAlive() i getState()

  • isAlive() — zwraca true, jeśli wątek został uruchomiony i jeszcze się nie zakończył (
    TERMINATED
    — to już false).
  • getState() — zwraca bieżący stan wątku (wartość z wyliczenia Thread.State).
Thread t = new Thread(() -> {});
System.out.println(t.isAlive()); // false (NEW)
t.start();
System.out.println(t.isAlive()); // true (RUNNABLE/WAITING/...)
t.join();
System.out.println(t.isAlive()); // false (TERMINATED)

5. Dlaczego nie można „zabić” wątku bezpośrednio i inne praktyczne wskazówki

Brak metody „kill”!

W Javie nie ma metody, która pozwalałaby „zabić” wątek na żądanie. Dlaczego? Bo to niebezpieczne: jeśli wątek trzyma jakiś zasób (plik, połączenie, blokadę), jego wymuszone zakończenie może pozostawić system w niespójnym stanie.

Przestarzałe metody: stop(), suspend(), resume()

W bardzo starych wersjach Javy istniały metody stop(), suspend(), resume(). Dziś są oznaczone jako @Deprecated, a ich użycie jest stanowczo odradzane. Dlaczego?

  • stop() może zabić wątek w dowolnym momencie, pozostawiając dane w niespójnym stanie.
  • suspend() może „zamrozić” wątek trzymający blokadę, a wtedy cały program się zawiesi.
  • resume() czasem nie może „odmrozić” wątku, jeśli ten już się zakończył.

Nowoczesne podejście:
Wątek powinien sam poprawnie się zakończyć, reagując na flagę przerwania (isInterrupted()) lub inne sygnały.

Najlepsze praktyki

  • Nie wywołuj metod oznaczonych jako przestarzałe.
  • Używaj flagi przerwania do zatrzymywania wątku (interrupt() i sprawdzanie isInterrupted()).
  • Obserwuj stany wątków podczas debugowania — to pomoże znaleźć zawieszenia i zakleszczenia (deadlocki).
  • Nie zapominaj o join(), gdy musisz zaczekać na zakończenie pracy wątku.

6. Typowe błędy przy pracy z cyklem życia wątku

Błąd nr 1: Ponowne uruchamianie wątku.
W Javie wątek można uruchomić tylko raz. Jeśli wywołasz start() drugi raz — otrzymasz IllegalThreadStateException. Jeśli musisz powtórzyć zadanie — utwórz nowy obiekt Thread.

Thread t = new Thread(() -> {});
t.start();
t.start(); // Rzuci wyjątek!

Błąd nr 2: Mylenie run() i start().
Wywołanie run() bezpośrednio nie uruchamia kodu w nowym wątku — wykonuje się on w bieżącym (np. w main). Tylko start() rzeczywiście uruchamia nowy wątek.

Błąd nr 3: Brak obsługi InterruptedException.
Jeśli wątek śpi lub czeka i zostanie przerwany, zostanie rzucony InterruptedException. Jeśli go zignorujesz, wątek może „zasnąć na zawsze” albo zakończyć się nieoczekiwanie.

Błąd nr 4: Brak sprawdzania stanu wątku.
Czasem program się zawiesza, ponieważ jeden wątek czeka na drugi, który już się zakończył albo nigdy nie rozpocznie pracy. Używaj getState() i isAlive() do diagnostyki.

Błąd nr 5: Używanie przestarzałych metod zarządzania wątkami.
Metody stop(), suspend(), resume() — to zło. Nie używaj ich, nawet jeśli bardzo kusi, by „szybko wszystko naprawić”.

1
Zadanie
JAVA 25 SELF, poziom 51, lekcja 2
Niedostępne
Kontrola zakończenia misji kosmicznej 🛰️
Kontrola zakończenia misji kosmicznej 🛰️
1
Zadanie
JAVA 25 SELF, poziom 51, lekcja 2
Niedostępne
Synchronizacja pracy dwóch pracowników 🤝
Synchronizacja pracy dwóch pracowników 🤝
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION