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 |
|---|---|
|
Wątek został utworzony, ale jeszcze nie uruchomiony (new Thread(...), lecz start() nie został wywołany) |
|
Wątek jest gotowy do wykonania lub wykonuje się w tej chwili |
|
Wątek oczekuje na zwolnienie monitora (zablokowany na bloku synchronized) |
|
Wątek czeka, aż inny wątek go „obudzi” (np. przez Object.wait()) |
|
Wątek czeka z limitem czasu (np. Thread.sleep(1000), wait(1000), join(1000)) |
|
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
(gotowy do pracy).RUNNABLE - 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
NEWRUNNABLETERMINATEDThread 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
RUNNABLETIMED_WAITINGRUNNABLETERMINATEDThread 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()
RUNNABLEWAITINGThread 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
BLOCKEDObject 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ł (
— to już false).TERMINATED - 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ć”.
GO TO FULL VERSION