CodeGym /Kurse /JAVA 25 SELF /Zustände und Lebenszyklus eines Threads

Zustände und Lebenszyklus eines Threads

JAVA 25 SELF
Level 51 , Lektion 2
Verfügbar

1. Grundlegende Thread-Zustände in Java

In Java ist ein Thread nicht einfach nur „gestartet“ oder „angehalten“. Er hat einen ganzen Lebenszyklus, und in jeder Phase verhält sich der Thread unterschiedlich. Das Verständnis dieser Phasen ist der Schlüssel zum Schreiben stabiler Multithread-Anwendungen und zur Fehlersuche bei seltsamen „Hängern“ und „unerwarteten Beendigungen“.

Welche Zustände gibt es?

Java definiert die folgenden grundlegenden Thread-Zustände (sie sind im Enum Thread.State aufgelistet):

Zustand Beschreibung
NEW
Der Thread wurde erstellt, aber noch nicht gestartet (new Thread(...), aber start() wurde nicht aufgerufen)
RUNNABLE
Der Thread ist bereit zur Ausführung oder wird gerade ausgeführt
BLOCKED
Der Thread wartet auf die Freigabe des Monitors (blockiert an einem synchronized-Block)
WAITING
Der Thread wartet darauf, dass ein anderer Thread ihn „aufweckt“ (z. B. über Object.wait())
TIMED_WAITING
Der Thread wartet mit Timeout (z. B. Thread.sleep(1000), wait(1000), join(1000))
TERMINATED
Der Thread hat die Ausführung beendet

Interessante Tatsache:
In älteren Büchern und Artikeln können Ihnen andere Bezeichnungen oder leicht andere Schemata begegnen. Seit Java 5+ gelten diese Zustände jedoch als Standard.

Visuelles Schema des Thread-Lebenszyklus

stateDiagram-v2
    [*] --> NEW
    NEW --> RUNNABLE: start()
    RUNNABLE --> BLOCKED: Versuch, in synchronized einzutreten, aber der Monitor ist belegt
    BLOCKED --> RUNNABLE: Monitor freigegeben
    RUNNABLE --> WAITING: wait(), join()
    RUNNABLE --> TIMED_WAITING: sleep(), wait(timeout), join(timeout)
    WAITING --> RUNNABLE: notify()/notifyAll(), join() ist beendet
    TIMED_WAITING --> RUNNABLE: Timeout abgelaufen / notify()/notifyAll()
    RUNNABLE --> TERMINATED: run() ist beendet
    WAITING --> TERMINATED: run() ist beendet (selten)
    TIMED_WAITING --> TERMINATED: run() ist beendet (selten)

2. Methoden zur Thread-Steuerung

Schlaf: Thread.sleep(long millis)

Manchmal muss ein Thread „schlafen“, um anderen nicht im Weg zu stehen oder auf ein Ereignis zu warten. Die Methode Thread.sleep(ms) versetzt den Thread für die angegebene Anzahl von Millisekunden in den Zustand

TIMED_WAITING
.

System.out.println("Der Thread schläft für 2 Sekunden...");
Thread.sleep(2000); // Schlafen 2 Sekunden
System.out.println("Der Thread ist aufgewacht!");
  • Nach dem Ende des Schlafs kehrt der Thread in den Zustand
    RUNNABLE
    zurück (arbeitsbereit).
  • Wenn der Thread während des Schlafs unterbrochen wird, wird eine InterruptedException ausgelöst.

Auf das Ende eines anderen Threads warten: join()

Manchmal muss man nicht nur einen Thread starten, sondern auch warten, bis er seine Arbeit beendet. Dafür gibt es in Java die Methode join():

Thread t = new Thread(() -> {
    System.out.println("Arbeite...");
    try { Thread.sleep(1000); } catch (InterruptedException e) {}
    System.out.println("Fertig!");
});
t.start();
System.out.println("Warte auf die Beendigung des Threads t...");
t.join(); // Der aktuelle Thread (z. B. main) wartet auf t
System.out.println("Thread t ist beendet!");

Hier beginnt der Haupt-Thread zu warten, sobald join() aufgerufen wird. Die ganze Zeit befindet er sich im Zustand

WAITING
, bis der Thread t seine Ausführung beendet. Es gibt auch eine Variante mit Timeout – join(long millis). In diesem Fall wartet der Thread nur eine begrenzte Zeit, und sein Zustand ist
TIMED_WAITING
.

Thread-Unterbrechung: interrupt()

Es gibt Situationen, in denen man einen Thread höflich bitten muss, seine Arbeit zu beenden, etwa wenn der Benutzer auf „Abbrechen“ klickt. Dafür wird die Methode interrupt() verwendet:

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // Wir arbeiten ...
    }
    System.out.println("Thread durch Unterbrechung beendet!");
});
t.start();
// ... nach einiger Zeit:
t.interrupt(); // Signal an den Thread: "Zeit anzuhalten"

Wichtig: Der Aufruf von interrupt() beendet den Thread nicht sofort. Er setzt lediglich ein spezielles Flag. Der Thread selbst sollte dieses Flag regelmäßig über isInterrupted() prüfen und sich selbst sauber beenden. Befindet sich der Thread in dieser Zeit im Schlaf (sleep()) oder im Warten (wait()), sieht er nicht nur das Flag, sondern erhält sofort die Ausnahme InterruptedException. Genau so sieht in Java der „korrekte“ Weg aus, Threads zu stoppen: Das Programm unterbricht sie nicht mit Gewalt, sondern teilt mit, dass es Zeit ist, zu enden.

3. Beispiele für Übergänge zwischen Zuständen

Schauen wir uns anhand von Beispielen an, wie ein Thread durch seine Zustände „reist“.

Beispiel 1:
NEW
RUNNABLE
TERMINATED

Thread t = new Thread(() -> System.out.println("Hallo!"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE (oder TERMINATED, wenn der Thread sehr schnell ist)
t.join();
System.out.println(t.getState()); // TERMINATED

Beispiel 2:
RUNNABLE
TIMED_WAITING
RUNNABLE
TERMINATED

Thread t = new Thread(() -> {
    try {
        System.out.println("Schlafe ein...");
        Thread.sleep(1000); // TIMED_WAITING
        System.out.println("Aufgewacht!");
    } catch (InterruptedException e) {
        System.out.println("Thread wurde unterbrochen!");
    }
});
t.start();

Beispiel 3:
RUNNABLE
WAITING
mit join()

Thread t1 = new Thread(() -> {
    try { Thread.sleep(500); } catch (InterruptedException ignored) {}
    System.out.println("t1 ist beendet");
});
Thread t2 = new Thread(() -> {
    try {
        t1.join(); // t2 wartet auf t1, befindet sich in WAITING
        System.out.println("t2 hat auf t1 gewartet");
    } catch (InterruptedException ignored) {}
});
t1.start();
t2.start();

Beispiel 4:
BLOCKED

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized (lock) {
        try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
        System.out.println("t1 hat den synchronized-Block verlassen");
    }
});
Thread t2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("t2 ist in den synchronized-Block eingetreten");
    }
});
t1.start();
Thread.sleep(100); // Geben wir t1 Zeit, lock zu übernehmen
t2.start();
Thread.sleep(100); // Geben wir t2 die Möglichkeit, in synchronized zu gelangen
System.out.println("Zustand von t2: " + t2.getState()); // BLOCKED

4. Wie ermittelt man den Zustand eines Threads? Die Methoden isAlive() und getState()

  • isAlive() – gibt true zurück, wenn der Thread gestartet ist und noch nicht beendet wurde (
    TERMINATED
    ergibt bereits false).
  • getState() – gibt den aktuellen Zustand des Threads zurück (einen Wert aus dem Enum 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. Warum man einen Thread nicht direkt „killen“ kann und weitere Praxistipps

Keine Methode „kill“!

In Java gibt es keine Methode, mit der man einen Thread „auf Kommando“ töten kann. Warum? Weil das unsicher ist: Hält der Thread gerade eine Ressource (Datei, Verbindung, Sperre), kann seine erzwungene Beendigung das System in einen inkonsistenten Zustand versetzen.

Veraltete Methoden: stop(), suspend(), resume()

In sehr alten Java-Versionen gab es die Methoden stop(), suspend(), resume(). Heute sind sie als @Deprecated markiert, und ihre Verwendung wird dringend abgeraten. Warum?

  • stop() kann den Thread jederzeit beenden und Daten in einem inkonsistenten Zustand zurücklassen.
  • suspend() kann einen Thread „einfrieren“, der eine Sperre hält – dann hängt das gesamte Programm.
  • resume() kann einen Thread manchmal nicht „auftauen“, wenn dieser bereits beendet ist.

Moderner Ansatz:
Der Thread sollte sich sauber selbst beenden, indem er auf das Unterbrechungs-Flag reagiert (isInterrupted()) oder auf andere Signale.

Best Practices

  • Rufen Sie keine Methoden auf, die als veraltet markiert sind.
  • Verwenden Sie das Unterbrechungs-Flag zum Stoppen des Threads (interrupt() und die Prüfung von isInterrupted()).
  • Beobachten Sie beim Debuggen die Thread-Zustände – das hilft, Hänger und Deadlocks zu finden.
  • Vergessen Sie join() nicht, wenn Sie auf die Beendigung eines Threads warten müssen.

6. Typische Fehler im Umgang mit dem Thread-Lebenszyklus

Fehler Nr. 1: Einen Thread erneut starten.
In Java kann ein Thread nur einmal gestartet werden. Wird start() ein zweites Mal aufgerufen, erhalten Sie eine IllegalThreadStateException. Wenn Sie die Aufgabe wiederholen müssen, erstellen Sie ein neues Thread-Objekt.

Thread t = new Thread(() -> {});
t.start();
t.start(); // Wirft eine Ausnahme!

Fehler Nr. 2: run() und start() verwechseln.
Ein direkter Aufruf von run() startet den Code nicht in einem neuen Thread – er wird im aktuellen Thread ausgeführt (z. B. in main). Nur start() startet tatsächlich einen neuen Thread.

Fehler Nr. 3: InterruptedException nicht behandeln.
Wenn ein Thread schläft oder wartet und unterbrochen wird, wird eine InterruptedException ausgelöst. Ignoriert man sie, kann der Thread „für immer einschlafen“ oder unerwartet enden.

Fehler Nr. 4: Den Zustand des Threads nicht prüfen.
Manchmal hängt ein Programm, weil ein Thread auf einen anderen wartet, der bereits beendet ist oder niemals starten wird. Verwenden Sie getState() und isAlive() zur Diagnose.

Fehler Nr. 5: Veraltete Thread-Steuerungsmethoden verwenden.
Die Methoden stop(), suspend(), resume() sind schlecht. Verwenden Sie sie nicht – auch dann nicht, wenn Sie „schnell alles reparieren“ möchten.

1
Aufgabe
JAVA 25 SELF, Level 51, Lektion 2
Gesperrt
Überwachung des Abschlusses einer Weltraummission 🛰️
Überwachung des Abschlusses einer Weltraummission 🛰️
1
Aufgabe
JAVA 25 SELF, Level 51, Lektion 2
Gesperrt
Synchronisation der Arbeit zweier Mitarbeiter 🤝
Synchronisation der Arbeit zweier Mitarbeiter 🤝
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION