Info B VL 17: Deadlocks
Objektorientiere Programmierung in Java 2003
Ute Schmid (Vorlesung) Elmar Ludwig ( ¨Ubung)
FB Mathematik/Informatik, Universit¨at Osnabr¨uck
Conditional Critical Regions
Konzept von Hoare, allgemeiner als Semaphoren.
Es existiert ein kritischer Abschnitt, wobei der Zugang durch eine Bedingung geschützt wird.
Typischerweise mit while-Schleife realisiert.
Würde man if anstelle von while verwenden, so würden aufgeweckte Threads nicht merken, dass ein anderer Thread die condition verändert hat.
(Während man schläft können andere arbeiten.)
synchronized(o) { synchronized(o) {
while (!condition ) { // condition == false
// ... // tu was
Monitore, CCRs, Semaphoren
Monitor: Ein Java-Objekt, das den Zugang zu synchronisierten (Instanz)-Methoden/Blöcken kontrolliert.
Conditional Critical Region: kritischer Abschnitt (in einem synchronisierten Block), bei dem auf Zugang gewartet wird (wait()), solange bis eine Bedingung erfüllt ist. Ein anderer Block macht die Bedingung wahr und gibt den kritischen Abschnitt frei (notify(),
notifyAll()).
Semaphoren: Sperren und Freigeben, realisiert durch Zähler und Warteschlange. Spezielle Technik, um
CCRs zu realisieren.
Deadlocks
Eine Situation in der zwei oder mehr
Prozesse/Threads nicht weiterarbeiten können, weil jeder darauf wartet, dass mindestens ein anderer etwas bestimmtes erledigt, heisst Deadlock.
Standardbeispiel: Dining Philosophers
Dining Philosophers
Wenn alle fünf Philosophen zur Gabel zu ihrer Rechten greifen, entsteht ein Deadlock!
Zweites Problem: Aushungern eines Philosophen (die anderen sind immer schneller beim Zugreifen). Kann mit Semaphoren oder kritischen Abschnitten alleine nicht verhindert werden. Java erlaubt immer einem beliebigen Thread, dass er zum Zug kommt.
Nicht Verklemmungsfrei
// Anzahl von Gabeln ist 5.
// Jede Gabel sei eine Semaphore, // die mit 1 initialisiert wird.
// F¨ur einen Philosophen i:
while(true) { think();
gabel[i].P();
gabel[(i+1)%anzahl].P(); // wg. i+1 gr¨oßer 4 eat();
gabel[i].V();
gabel[(i+1)%anzahl].V();
}
Bedingungen für Deadlocks
Exklusive Belegung: Betriebsmittel sind entweder von genau einem Prozess belegt oder frei. (eine Gabel)
Belegen und Warten: Prozesse belegen Betriebsmittel und warten während der Belegung auf die Zuteilung weiterer Betriebsmittel. (linke und rechte Gabel)
Kein zwangsweises Freigeben: Betriebsmittel können nicht entzogen werden, sondern müssen vom Prozess zurückgegeben werden (Hinlegen einer Gabel)
Zyklische Wartebedingung: Es muss einen Ring aus zwei oder mehr Prozessen bestehen, bei der jeder Prozess auf ein von einem anderen Prozess aus der Kette belegtes Betriebsmittel wartet. (5 Philosophen)
Beispiellösungen
Lösung mit globaler Kontrolle: Manager.java
Bedingter Zugriff auf Gabeln: Philosopher2.java
Deadlocks durch falsche Anordnung
// When two threads try to lock two objects, deadlock can occur // unless they always request the locks in the same order.
final Object resource1 = new Object(); // Here are two objects to lock final Object resource2 = new Object();
Thread t1 = new Thread(new Runnable() { // Locks resource1 public void run() { // then resource2
synchronized(resource1) {
synchronized(resource2) { compute(); } }
} });
Thread t2 = new Thread(new Runnable() { // Locks resource2 public void run() { // then resource1
synchronized(resource2) {
synchronized(resource1) { compute(); } } }
});
t1.start(); // Locks resource1
t2.start(); // Locks resource2 and now neither thread can progress!
Threads: Ergänzungen
Threads können durch setDaemon(true) zu
Dämon-Threads gemacht werden: Der Interpreter wird beendet, wenn alle Nicht-Dämon-Threads beendet
sind.
Threads können Thread-Gruppen zugeordnet werden und dann gemeinsam behandelt werden. Ohne
explizite Angabe einer Gruppe gehört ein Thread zur Gruppe ‘System’.
Threads und Collections
Vordefinierte Klassen, die Collection, Set, List oder Map implementieren, haben in der Regel keine synchronized()-Methoden (z.B. ArrayList). Es können synchronisierte Wrapper-Objekte erzeugt
werden:
List synclist =
Collections.synchronizedList(list);
Map syncmap =
Collections.synchronizedMap(map);
Unterbrechen von Threads
Ordentliches Unterbrechen eines Threads durch interrupt().
wait() und sleep() erlauben einen Interrupt, also kann ein Thread nicht “mitten beim Arbeiten”
angehalten werden.
class T extends Thread { public void run() {
while(true) { // This thread runs until asked to stop
process(); // Do something
try { Thread.sleep(1000); } // Wait 1000 millisecs catch (InterruptedException e) { // Handle the interrupt
return;
} } }
Pipe-Ströme
Pipes werden benutzt, um von einem Thread erzeugte Daten einem anderen Thread zur Verfügung zu stellen.
Klassen PipedReader/PipedWriter (Character-Stream) bzw.
PipedInputStream/PipedOutputStream aus java.io
Beispiel (1)
Reverse Sort
Reverse Sort
Reverse
Reverse List of reversed
words
List of words List of reversed
sorted words
List of rhyming words
List of words List of rhyming
words
Code: RhymingWords.java
nutzt die verschieden kombinierbaren Komponenten und
Beispiel (2)
Ohne Pipe-Ströme müssen die Operationen Umdrehen, Sortieren, Umdrehen sequentiell
abgearbeitet werden, da jedes Zwischenergebnis
explizit, z. B. in einer Datei, gespeichert werden muss.
Mit Pipe-Strömen kann der Output einer Operation direkt an die nachfolgende Operation übergeben werden.
In der main-Methode wird ein Reader-Objekt erzeugt.
Die Ermittlung der reimenden Worte wird über den verschachtelten Methodenaufruf
reverse(sort(reverse(words))) realisiert.
Die Methoden reverse() und sort() arbeiten mit Threads und kommunizieren über Pipe-Ströme
Beispiel (3)
Ein PipeReader wird “auf” einem PipeWriter erzeugt:
Was auf den PipeWriter geschrieben wird, kann vom PipeReader gelesen werden!
Dazu gibt es (für die Thread-Objekte) einen
BufferedReader, der die Eingabe (aus einer Quelle, z.B. der Pipe) erhält, und einen PrintWriter, der die Ausgabe in eine Sink (z.B. Pipe) schreibt.