75. Deadlocks

Synchronisierungen können sogenannte Deadlocks (Verklemmung) erzeugen: Zwei oder mehr Threads können nicht fortsetzen, weil sie auf ein Ereignis warten, welches nur ein Thread aus dieser Menge bewirken kann.

‘Producer-Consumer’ Problem:

Regel:

  • Der Producer kann nur etwas in die Pipeline legen, wenn diese leer ist und wartet bis diese leer ist

  • Der Consumer kann nur etwas aus der Pipeline holen, wenn diese etwas enthält und wartet bis diese etwas enthält

Problem:

Synchronisierte Threads beschlagnahmen das Lock der Methode put() oder lock() für sich und lassen einen Wechsel in einen anderen Thread nicht mehr zu z.B:

  1. die Pipeline ist leer

  2. Thread 0 möchte etwas herausholen -> wartet in der gelockten Methode get() bis etwas hineinkommt

  3. Thread 1 möchte etwas hineintun -> kommt nicht in die put(), da diese mit dem gleichen Lock auf get() synchronisiert ist

Lösung:

Object.wait()

  • gibt das Lock dieses Objekts wieder frei

  • Thread wird in einen Ruhezustand versetzt (undefinierte Länge)

Object.wait(long timeout)

  • wie Object.wait() aber mit definierter Zeitspanne für Ruhezustand des Threads

statt

while (data != null){
    ;  //busy waiting
}

schreiben wir

while (data != null){
    try
    {
        wait();
    }
    catch (InterruptedException e )
    {
        e.printStackTrace();
    }
}

Allerdings muss ein Thread wieder aufgeweckt werden, sobald dies Sinn macht

Object.notify()

  • weckt einen einzelnen Thread auf, der zuvor ein wait() erhalten hat -> es kann nie explizit gesagt werden welcher

  • wird nur dann eingesetzt wenn es keine Rolle spielt welcher Thread als Erstes aufgeweckt wird (weil sie Ähnliches machen)

Object.notifyAll()

  • weckt alle Threads auf, die zuvor ein wait() erhalten haben

Wichtig ist, dass zugeordnet wird, die Threads von welchem Lock aufgeweckt werden sollen (Die Methoden wait() und notify() sind lock-spezifisch). Dafür muss zunächst definiert werden für welches Lock ein wait() gelten soll:

while (data != null){
    try
    {
        this .wait();
    }
    catch (InterruptedException e )
    {
        e.printStackTrace();
    }
}

Hier wird auf das aktuelle Pipeline Objekt selbst gelockt, also this.wait() Ansonsten muss auf das Lock eines bestimmten Objekt angegeben werden (z.B. lock.wait())

Die notifyAll() Methode wird dann nach dem Abschluss (aber vor dem return) eines synchronisierten Threads aufgerufen: this.notifyAll();

Faustregel

Wo ein wait() ist muss auch ein notify() sein.

Attention

Da man nie weiß, welcher Thread den anderen wieder aufweckt kann man in Endlosschleifen nicht mit if-else Bedingungen arbeiten:

if(data != null){
    try
    {
        this .wait();
    }
    catch (InterruptedException e )
    {
        e.printStackTrace();
    }
}

Hier kann es sein, dass die Bedingung zum Zeitpunkt des Aufweckens nicht mehr gilt und die Aktionen zu unrecht durchgeführt werden z.B. ein Producer weckt einen anderen Producer -> diese überschreibt die Daten wieder

Faustregel

Eine wait() Anweisung muss in eine while-Schleifen sein

../../../_images/summary.png

Zusammenfassende Grafik