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:
Pipeline.java: Enthält put() und get() MethodeProducer.java: Legt etwas in die Pipeline -> put()Consumer.java: Holt etwas aus der Pipeline -> get()
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:
die Pipeline ist leer
Thread 0 möchte etwas herausholen -> wartet in der gelockten Methode get() bis etwas hineinkommt
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
Zusammenfassende Grafik