CodeGym /Kurse /JAVA 25 SELF /Deadlock: Ursachen, Beispiele, Behebung

Deadlock: Ursachen, Beispiele, Behebung

JAVA 25 SELF
Level 53 , Lektion 0
Verfügbar

1. Tiefer einsteigen in Deadlocks

Ein Deadlock („gegenseitige Blockierung“) ist eine Situation, in der zwei oder mehr Threads einander unbegrenzt lange warten lassen: Jeder hält eine Ressource und versucht, eine weitere zu bekommen, die bereits von einem anderen Thread gehalten wird. Am Ende kann niemand fortfahren und das Programm hängt sich auf. Es ist wie zwei Autos auf einer schmalen Brücke, Stoßstange an Stoßstange: Solange keines zurücksetzt, fährt niemand weiter.

In Java ist ein Deadlock kein Ausnahmefehler (Exception), sondern ein „Einfrieren“ des Programms. Threads beenden sich nicht und stürzen nicht mit einem Fehler ab, sondern warten einfach endlos aufeinander. Daher sind solche Fehler tückisch: Sie zeigen sich nur „unter bestimmten Umständen“.

Vier Bedingungen für das Entstehen eines Deadlocks

  • 1. Gegenseitiger Ausschluss (Mutual Exclusion)
    Eine Ressource kann jeweils nur von einem Thread gehalten werden (z. B. ein Objektmonitor, eine Datei).
  • 2. Halten und Warten (Hold and Wait)
    Ein Thread hält eine Ressource und versucht, eine zweite zu bekommen, ohne die erste freizugeben.
  • 3. Keine Zwangsentziehung (No Preemption)
    Eine Ressource kann einem Thread nicht „weggenommen“ werden: Nur der Thread selbst kann sie freigeben.
  • 4. Zyklisches Warten (Circular Wait)
    Es gibt eine geschlossene Kette, in der jeder Thread auf eine Ressource wartet, die vom nächsten in der Kette gehalten wird.

Ein Deadlock ist nur möglich, wenn ALLE diese Bedingungen gleichzeitig erfüllt sind. Es reicht, mindestens eine zu verletzen – dann ist eine gegenseitige Blockierung unmöglich.

2. Codebeispiel mit Deadlock

Schema

  • Es gibt zwei Ressourcen: lock1 und lock2 (gewöhnliche Objekte).
  • Thread A erfasst zuerst lock1 und versucht dann, lock2 zu bekommen.
  • Thread B erfasst zuerst lock2 und versucht dann, lock1 zu bekommen.

Beispiel (zu Hause bitte nicht nachmachen!)

public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        // Thread 1
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("Thread 1: hat lock1 gesperrt");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                System.out.println("Thread 1: versucht, lock2 zu sperren");
                synchronized (lock2) {
                    System.out.println("Thread 1: hat lock2 gesperrt");
                }
            }
        });

        // Thread 2
        Thread thread2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("Thread 2: hat lock2 gesperrt");
                try { Thread.sleep(100); } catch (InterruptedException ignored) {}
                System.out.println("Thread 2: versucht, lock1 zu sperren");
                synchronized (lock1) {
                    System.out.println("Thread 2: hat lock1 gesperrt");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

Was passiert beim Start?

  • Thread 1 erfasst lock1, Thread 2 — lock2.
  • Beide „schlafen“ für 100 Millisekunden und schaffen es, die erste Sperre zu setzen.
  • Dann versucht jeder, die zweite Sperre zu setzen, die bereits vom anderen Thread gehalten wird.
  • Beide Threads warten endlos aufeinander — es entsteht ein Deadlock.

In der Konsole sehen Sie:

Thread 1: hat lock1 gesperrt
Thread 2: hat lock2 gesperrt
Thread 1: versucht, lock2 zu sperren
Thread 2: versucht, lock1 zu sperren

Warum entsteht ein Deadlock? Ausführliche Analyse

  • Gegenseitiger Ausschluss: Jede Sperre kann jeweils nur von einem Thread gehalten werden.
  • Halten und Warten: Thread 1 hält lock1 und wartet auf lock2; Thread 2 hält lock2 und wartet auf lock1.
  • Keine Zwangsentziehung: Niemand kann die Sperre von außen „herausziehen“ — nur durch das Verlassen des synchronized-Blocks.
  • Zyklisches Warten: Das Warten ist zwischen zwei Threads im Kreis geschlossen.

3. Deadlocks vermeiden und verhindern

Ressourcen in derselben Reihenfolge sperren

Die wichtigste Regel: Wenn mehrere Threads mehrere Ressourcen sperren müssen, tun Sie dies immer in derselben Reihenfolge. Zum Beispiel: zuerst lock1, dann lock2 — und zwar an allen Stellen im Code.

public void doSomething() {
    Object firstLock = lock1;
    Object secondLock = lock2; // einheitliche Reihenfolge "lock1 -> lock2"
    synchronized (firstLock) {
        synchronized (secondLock) {
            // Arbeit mit beiden Ressourcen
        }
    }
}

Bei einheitlicher Reihenfolge ist zyklisches Warten unmöglich — die Deadlock-Bedingung wird verletzt.

tryLock mit Timeout verwenden (ReentrantLock)

Wenn im Voraus nicht klar ist, welche Ressourcen benötigt werden, verwenden Sie ReentrantLock und die Methode tryLock, um nicht endlos zu warten.

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class TryLockDemo {
    private static final ReentrantLock lock1 = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    public void doWork() {
        try {
            if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
                try {
                    if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                        try {
                            // Kritischer Abschnitt
                        } finally {
                            lock2.unlock();
                        }
                    } else {
                        System.out.println("Konnte lock2 nicht sperren, breche ab");
                    }
                } finally {
                    lock1.unlock();
                }
            } else {
                System.out.println("Konnte lock1 nicht sperren, breche ab");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Plus: Man kann den Vorgang abbrechen und später erneut versuchen. Minus: Der Code ist komplexer, aber Deadlocks sind ausgeschlossen.

Halten Sie Sperren möglichst kurz

Halten Sie eine Sperre so kurz wie möglich. Führen Sie innerhalb von synchronized/lock nur das Nötigste aus; alles Andere gehört außerhalb der kritischen Sektion.

Verschachtelte Sperren vermeiden

Je weniger verschachtelte synchronized/lock-Blöcke, desto geringer das Deadlock-Risiko. Wenn möglich, verwenden Sie eine einzige Sperre für zusammengehörige Ressourcen.

Verwenden Sie fertige thread-sichere Strukturen

Die Standardbibliothek löst bereits viele Aufgaben: Collections wie ConcurrentHashMap und andere Klassen aus java.util.concurrent minimieren das Deadlock-Risiko und vereinfachen den Code.

4. Deadlock-Diagnose

Thread Dump und jstack

Thread Dump — eine Momentaufnahme des Zustands aller Threads in der JVM.

So erhalten Sie ihn in der Konsole:

jstack <pid>

Dabei ist <pid> die Kennung des Java-Prozesses (ermittelbar über jps).

In IDEs gibt es gewöhnlich eine „Thread Dump“-Schaltfläche in der Debug-Ansicht.

Suchen Sie im Dump nach:

  • Statuswerten BLOCKED oder WAITING.
  • Meldungen wie waiting to lock ... und locked ....
  • Der JVM-Meldung: "Found one Java-level deadlock:" — wenn eine gegenseitige Blockierung erkannt wurde.

Beispielausschnitt eines Dumps:

"Thread-1":
  waiting to lock monitor 0x000000001e4000, (object 0x7f8a5c00, a java.lang.Object),
  which is held by "Thread-2"
"Thread-2":
  waiting to lock monitor 0x000000001e3000, (object 0x7f8a5c10, a java.lang.Object),
  which is held by "Thread-1"

Das ist ein Deadlock: Die Threads „halten“ einander fest.

VisualVM und weitere Werkzeuge

  • VisualVM — ein kostenloses Werkzeug zur Thread-Analyse und Deadlock-Erkennung.
  • Java Mission Control / Flight Recorder — fortgeschrittenes Monitoring und Profiling.

In VisualVM können Sie den Thread-Baum, deren Zustände ansehen und eine Benachrichtigung über eine gegenseitige Blockierung erhalten.

Tabelle: „Wie vermeidet man Deadlocks“

Ursache für Deadlock Wie vermeiden
Ressourcen in unterschiedlicher Reihenfolge sperren Immer dieselbe Reihenfolge der Ressourcensperren einhalten
Verschachteltes synchronized Verschachtelung minimieren, nach Möglichkeit eine einzige Sperre verwenden
Langes Halten einer Sperre Arbeit innerhalb der kritischen Sektion reduzieren
Gleichzeitige Verwendung mehrerer Sperren tryLock mit Timeout verwenden und bei Bedarf zurücksetzen
Nicht thread-sichere Collections Konkurrierende Collections verwenden (ConcurrentHashMap u. a.)

5. Typische Fehler im Umgang mit Deadlocks

Fehler Nr. 1: Ressourcen in unterschiedlicher Reihenfolge sperren. Der häufigste Grund — verschiedene Threads nehmen Sperren in unterschiedlicher Reihenfolge. Selbst wenn es „schneller“ scheint, halten Sie sich an eine einheitliche Reihenfolge — sonst ist zyklisches Warten garantiert.

Fehler Nr. 2: Verschachtelte synchronized-Blöcke ohne Notwendigkeit. Überflüssige Verschachtelung erhöht das Deadlock-Risiko und verkompliziert den Code. Vereinfachen Sie das Sperrenmodell.

Fehler Nr. 3: tryLock und Timeouts ignorieren. Eine Sperre per synchronized lässt den Thread endlos warten. Wenn Sie nicht sicher sind, verwenden Sie tryLock mit Timeout und eine Abbruch-/Rollback-Logik.

Fehler Nr. 4: Lange Ausführung innerhalb einer Sperre. Netzwerkaufrufe, I/O und rechenintensive Operationen unter einer Sperre erhöhen das Risiko deutlich. Verlegen Sie sie außerhalb der kritischen Sektion.

Fehler Nr. 5: Thread Dump nicht analysieren. Der Thread Dump ist Ihr bester Freund bei der Suche nach gegenseitigen Blockierungen. Verwenden Sie jstack und analysieren Sie die Zustände BLOCKED/WAITING, die Ketten „holding/waiting to lock“.

1
Aufgabe
JAVA 25 SELF, Level 53, Lektion 0
Gesperrt
Staffelstabübergabe zwischen Robotern: Deadlock-Gefahr 🤖
Staffelstabübergabe zwischen Robotern: Deadlock-Gefahr 🤖
1
Aufgabe
JAVA 25 SELF, Level 53, Lektion 0
Gesperrt
Rettung vor einer Sackgasse: Clevere Ingenieure mit Timeout 🛠️
Rettung vor einer Sackgasse: Clevere Ingenieure mit Timeout 🛠️
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION