Thread-Koordination
Das Problem des schlafenden Friseurs

Das Problem des schlafenden Friseurs
- In dem Friseurladen arbeitet 1 Friseur
- Es gibt 1 Friseurstuhl und n Stühle für wartende Kunden
- Falls kein Kunde anwesend ist, setzt sich der Friseur auf seinen Friseurstuhl und schläft.
- Ein neuer Kunde muss den Friseur aufwecken
- Weitere Kunden nehmen auf einem der n Stühle Platz oder verlassen den Laden falls alle Stühle besetzt sind.
Das Problem
- Ein Thread (Producer) erzeugt Daten und stellt sie in eine Queue ein anderer Thread (Consumer) soll sie verarbeiten und aus der Queue herausnehmen
- Wie vermeidet man, dass der Consumer ständig nachfragen muss, ob etwas für ihn vorliegt?
- Wie signalisiert man dem Consumer-Thread, dass Arbeit in der Queue vorhanden ist?
- Wie sorgt man dafür, dass der Producer-Thread aufhört Daten in die Queue zu stellen, wenn der Consumer-Thread nicht mehr nachkommt?
Man benötigt eine Lösung, die folgendes bietet:
- Ein Thread kann sich schlafen legen
- Ein anderer Thread kann diesen Thread wieder aufwecken
Der Consumer legt sich schlafen wenn die Queue leer ist und weckt andere Threads auf, wenn er etwas aus der Queue genommen hat.
Der Producer legt sich schlafen wenn die Queue voll ist und weckt andere Threads auf, wenn er etwas in die Queue hineingelegt hat.
Die Lösung: wait() und notify()
- Die Klasse
Object
bietet zwei Methoden an, um das beschriebene Problem zu lösen wait()
– legt einen Thread schlafennotify()
– weckt einen der Threads, die sich mitwait()
schlafen gelegt haben wieder aufnotifyAll()
– weckt alle wartenden Threads wieder aufwait()
,notify()
undnotifyAll()
dürfen nur auf Objekten aufgerufen werden für die der Thread das Lock hältwait()
sollte immer in einer Schleife verwendet werden
Zustandsdiagramm mit Wait-Pool
Durch wait()
und notify()
kommt ein neuer (und der letzte) Zustand bei Threads hinzu: blockiert im Wait-Pool. Wenn ein Thread auf einem Objekt wait()
aufruft, so wandert er in diesen Zustand. Das Lock gibt er zurück, obwohl der mitten in einem synchronized
steht. Dies ist eine Besonderheit von wait()
: Ein Aufruf der Methode gibt das Token zurück.
Der Thread verbleibt jetzt solange im Wait-Pool, bis ein anderer Thread auf demselben Objekt notify()
oder notifyAll()
aufruft. Bei notify()
wandert ein (zufällig ausgesuchter) Thread in den Lock-Pool, bei notifyAll()
alle. Da der Thread, der notify()
aufgerufen hat, in diesem Moment noch das Lock hält, bleiben die geweckten Threads solange im Lock-Pool, bis einer von ihnen das Lock bekommen kann, weil der andere Thread es beim Verlassen des kritischen Abschnitts zurückgegeben hat.
Beispiel: Producer / Consumer
public class Producer implements Runnable {
Queue<String> queue;
public Producer(Queue<String> queue) {
this.queue = queue;
}
public void run() { ... }
}
public class Consumer implements Runnable {
Queue<String> queue;
public Consumer(Queue<String> queue) {
this.queue = queue;
}
public void run() { ... }
}
Beispiel: Producer – run()-Methode
int i = 0;
try {
while (true) {
synchronized (queue) {
while (queue.size() > 10) {
queue.wait();
}
queue.add("P" + ++i);
queue.notifyAll();
}
Thread.sleep(100);
}
}
catch (InterruptedException e) {
}
Normalerweise würde man den Code, der die Größe der Queue testet und entsprechend das wait()
bzw. notify()
macht in die Queue selbst verlagern, da die hier durchgeführte Synchronisation von außen sehr fehleranfällig ist.
Beispiel: Consumer – run()-Methode
try {
while (true) {
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
String element = queue.poll();
queue.notifyAll();
System.out.println(Thread.currentThread().getName()
+ ": " + element);
}
Thread.sleep(100);
}
}
catch (InterruptedException e) {
}
Beispiel: Start-Code
Queue<String> queue = new LinkedList<String>();
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
Thread tp = new Thread(producer, "Producer");
Thread c1 = new Thread(consumer, "Consumer 1");
Thread c2 = new Thread(consumer, "Consumer 2");
Thread c3 = new Thread(consumer, "Consumer 3");
tp.start();
c1.start();
c3.start();
c2.start();
Consumer 1: P1
Consumer 2: P2
Consumer 1: P3
Consumer 2: P4
Consumer 1: P5
Consumer 3: P6
Wie funktionieren wait() und notify()?
Siehe externe Folien…
Was muss ich mir merken…
- Man kann
wait()
undnotify()
auf jedem beliebigen Objekten verwenden - Man darf die Methoden nur in einem synchronized-Block verwenden, der dasselbe Objekt als Lock verwendet
- Test, der zum
wait()
führt sollte immer in einer Schleife ausgeführt werden, da er noch einmal wiederholt werden muss – der Thread weiß nicht warum er geweckt wurde - Bei Producer-Consumer muss man immer
notifyAll()
verwenden, danotify()
zum Verhungern führen kann