Java-Deadlocks in der NetBeans-IDE finden

Die NetBeans-IDE half mir mehr als einmal, einen Deadlock aufzuspüren. Wie sich dies in der Praxis gestaltet, beschreibe ich hier kurz.

Deadlock-Beispiel

Ich benutze das Beispiel Deadlock der Java Tutorials und modifiziere es geringfügig, damit die Ausgaben das Problem klarer offen legen: Statt die Friend-Klasseninstanzen selbst als Monitor zu benutzen (synchronized in der Methodensignatur), nehme ich die final name-Fields der Friend-Objekte – die String-Objekte Alphonse und Gaston – diese werden in der IDE im Debugging-Fenster bei Deadlocks als Monitor angezeigt. Zusätzlich lasse ich ausgeben, welcher Thread gerade welchen Monitor benötigt und ob er diesen erhält, dazu gebe ich den Threads Namen:

public class DeadlockExample {

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        Thread alphonseThread = new Thread(new Runnable() {
            @Override
            public void run() {
                alphonse.bow(gaston);
            }
        });
        alphonseThread.setName(alphonse.name);
        alphonseThread.start();
// Verhindert, dass gastonThread Lock erhält, während alphonseThread läuft, ggf. Zeit verlängern
//        try {
//            TimeUnit.SECONDS.sleep(1);
//        } catch (InterruptedException ex) {
//            Logger.getLogger(DeadlockExample.class.getName()).log(Level.SEVERE, null, ex);
//        }
        Thread gastonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                gaston.bow(alphonse);
            }
        });
        gastonThread.setName(gaston.name);
        gastonThread.start();
    }

    private static class Friend {

        private final String name;

        private Friend(String name) {
            this.name = name;
        }

        public void bow(Friend bower) {
            String threadName = Thread.currentThread().getName();
            System.out.println("Thread " + threadName + ".bow(): Thread " + threadName + " needs monitor " + name);
            synchronized (name) {
                System.out.println("Thread " + threadName + ".bow(): Thread " + threadName + " got monitor " + name);
                System.out.format("    %s: %s" + " has bowed to me!%n", name, bower.name);
                bower.bowBack(this);
            }
        }

        public void bowBack(Friend bower) {
            String threadName = Thread.currentThread().getName();
            System.out.println("Thread " + threadName + ".bowBack(): Thread " + threadName + " needs monitor " + name);
            synchronized (name) {
                System.out.println("Thread " + threadName + ".bowBack(): Thread " + threadName + " got monitor " + name);
                System.out.format("    %s: %s" + "has bowed back to me!%n", name, bower.name);
            }
        }
    }
}

Das Beispiel produziert nicht garantiert einen Deadlock: Falls im ersten Thread alphonse.bow(gaston) komplett ausgeführt wird, bevor der zweite Thread gestartet und bis zum synchronized-Abschnitt ausgeführt wird, wo er den Monitor erhält, gibt es keine Probleme. Das sollte ziemlich sicher der Fall sein durch Auskommentieren des sleep-Aufrufs in main(). Ein typischer Ablauf sieht so aus:

Thread Alphonse.bow(): Thread Alphonse needs monitor Alphonse
Thread Alphonse.bow(): Thread Alphonse got monitor Alphonse
    Alphonse: Gaston has bowed to me!
Thread Gaston.bow(): Thread Gaston needs monitor Gaston
Thread Alphonse.bowBack(): Thread Alphonse needs monitor Gaston
Thread Gaston.bow(): Thread Gaston got monitor Gaston
    Gaston: Alphonse has bowed to me!
Thread Gaston.bowBack(): Thread Gaston needs monitor Alphonse

In der Ausgabe ist zu erkennen, dass es in der Methode bowBack() nicht weiter geht:

  • bowBack(), aufgerufen im Thread Alphonse, benötigt den Monitor Gaston
  • Diesen Monitor erhält kurz darauf der Thread Gaston, während Thread Alphonse pausiert
  • bowBack(), aufgerufen im Thread Gaston, benötigt den Monitor Alphonse, den der Thread Alphonse in der 2. Zeile der Ausgabe erhielt

Jeder Thread wartet darauf, dass der andere den erforderlichen Monitor freigibt, was aber nicht geschehen kann, beide Threads blockieren sich gegenseitig, beenden sich nie von selbst.

Deadlocks in der NetBeans-IDE feststellen

Läuft das Programm während des Debuggens nicht weiter, und ein Deadlock ist nicht prinzipiell auszuschließen, kann NetBeans den Deadlock finden via Menü Debug > Check for Deadlock. Der erste Verdacht ergibt sich, falls Threads nicht beendet sind, die es längst sein sollten. In der folgenden Abbildung sind die beiden Threads Alphonse und Gaston noch da, beenden sich auch nicht von alleine, obwohl der Code nur Millisekunden benötigen sollte.

netbeans-dedlock-01

Nach Auswahl von Debug > Check for Deadlock wird der Deadlock angezeigt:

netbeans-dedlock-02

Hier ist abzulesen, welcher Thread welchen Monitor hat, die Methode, bei der der Deadlock entstand und welchen Monitor die blockierten Threads benötigen. Ein Doppelklick auf den Methodennamen bowBack() öffnet die Java-Datei im Editor und der Cursor steht in der entsprechenden Zeile (59).

Eine einfache Lösung wäre, bowBack() außerhalb des synchronized-Blocks auszuführen, dann wird der Monitor vorher zurückgegeben:

public void bow(Friend bower) {
    String threadName = Thread.currentThread().getName();
    System.out.println("Thread " + threadName + ".bow(): Thread " + threadName + " needs monitor " + name);
    synchronized (name) {
        System.out.println("Thread " + threadName + ".bow(): Thread " + threadName + " got monitor " + name);
        System.out.format("    %s: %s" + " has bowed to me!%n", name, bower.name);
    }
    bower.bowBack(this);
}

Stichwörter: ,

Zu diesem Artikel können keine Kommentare mehr geschrieben werden.