Galileo Computing :: Java 7 - Mehr als eine Insel - 2 Threads und nebenläufige Programmierung
Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Neues in Java 7
2 Threads und nebenläufige Programmierung
3 Datenstrukturen und Algorithmen
4 Raum und Zeit
5 Dateien, Verzeichnisse und Dateizugriffe
6 Datenströme
7 Die eXtensible Markup Language (XML)
8 Dateiformate
9 Grafische Oberflächen mit Swing
10 Grafikprogrammierung
11 Netzwerkprogrammierung
12 Verteilte Programmierung mit RMI
13 RESTful und SOAP Web-Services
14 JavaServer Pages und Servlets
15 Applets
16 Datenbankmanagement mit JDBC
17 Technologien für die Infrastruktur
18 Reflection und Annotationen
19 Dynamische Übersetzung und Skriptsprachen
20 Logging und Monitoring
21 Java Native Interface (JNI)
22 Sicherheitskonzepte
23 Dienstprogramme für die Java-Umgebung
Stichwort

Download:
- openbook, ca. 21,3 MB
Buch bestellen
Ihre Meinung?

Spacer
Java 7 - Mehr als eine Insel von Christian Ullenboom
Das Handbuch zu den Java SE-Bibliotheken
Buch: Java 7 - Mehr als eine Insel

Java 7 - Mehr als eine Insel
Galileo Computing
1433 S., 2012, geb.
49,90 Euro, ISBN 978-3-8362-1507-7
Pfeil 2 Threads und nebenläufige Programmierung
Pfeil 2.1 Threads erzeugen
Pfeil 2.1.1 Threads über die Schnittstelle Runnable implementieren
Pfeil 2.1.2 Thread mit Runnable starten
Pfeil 2.1.3 Die Klasse Thread erweitern
Pfeil 2.2 Thread-Eigenschaften und -Zustände
Pfeil 2.2.1 Der Name eines Threads
Pfeil 2.2.2 Wer bin ich?
Pfeil 2.2.3 Die Zustände eines Threads *
Pfeil 2.2.4 Schläfer gesucht
Pfeil 2.2.5 Mit yield() auf Rechenzeit verzichten
Pfeil 2.2.6 Der Thread als Dämon
Pfeil 2.2.7 Das Ende eines Threads
Pfeil 2.2.8 Einen Thread höflich mit Interrupt beenden
Pfeil 2.2.9 UncaughtExceptionHandler für unbehandelte Ausnahmen
Pfeil 2.2.10 Der stop() von außen und die Rettung mit ThreadDeath *
Pfeil 2.2.11 Ein Rendezvous mit join() *
Pfeil 2.2.12 Arbeit niederlegen und wieder aufnehmen *
Pfeil 2.2.13 Priorität *
Pfeil 2.3 Der Ausführer (Executor) kommt
Pfeil 2.3.1 Die Schnittstelle Executor
Pfeil 2.3.2 Die Thread-Pools
Pfeil 2.3.3 Threads mit Rückgabe über Callable
Pfeil 2.3.4 Mehrere Callable abarbeiten
Pfeil 2.3.5 ScheduledExecutorService für wiederholende Ausgaben und Zeitsteuerungen nutzen
Pfeil 2.4 Synchronisation über kritische Abschnitte
Pfeil 2.4.1 Gemeinsam genutzte Daten
Pfeil 2.4.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
Pfeil 2.4.3 Punkte parallel initialisieren
Pfeil 2.4.4 i++ sieht atomar aus, ist es aber nicht *
Pfeil 2.4.5 Kritische Abschnitte schützen
Pfeil 2.4.6 Kritische Abschnitte mit ReentrantLock schützen
Pfeil 2.4.7 Synchronisieren mit synchronized
Pfeil 2.4.8 Synchronized-Methoden der Klasse StringBuffer *
Pfeil 2.4.9 Mit synchronized synchronisierte Blöcke
Pfeil 2.4.10 Dann machen wir doch gleich alles synchronisiert!
Pfeil 2.4.11 Lock-Freigabe im Fall von Exceptions
Pfeil 2.4.12 Deadlocks
Pfeil 2.4.13 Mit synchronized nachträglich synchronisieren *
Pfeil 2.4.14 Monitore sind reentrant – gut für die Geschwindigkeit *
Pfeil 2.4.15 Synchronisierte Methodenaufrufe zusammenfassen *
Pfeil 2.5 Synchronisation über Warten und Benachrichtigen
Pfeil 2.5.1 Die Schnittstelle Condition
Pfeil 2.5.2 It’s Disco-Time *
Pfeil 2.5.3 Warten mit wait() und Aufwecken mit notify() *
Pfeil 2.5.4 Falls der Lock fehlt: IllegalMonitorStateException *
Pfeil 2.6 Datensynchronisation durch besondere Concurrency-Klassen *
Pfeil 2.6.1 Semaphor
Pfeil 2.6.2 Barrier und Austausch
Pfeil 2.6.3 Stop and go mit Exchanger
Pfeil 2.7 Atomare Operationen und frische Werte mit volatile *
Pfeil 2.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
Pfeil 2.7.2 Das Paket java.util.concurrent.atomic
Pfeil 2.8 Teile und herrsche mit Fork und Join *
Pfeil 2.8.1 Algorithmendesign per »teile und herrsche«
Pfeil 2.8.2 Paralleles Lösen von D&C-Algorithmen
Pfeil 2.8.3 Fork und Join
Pfeil 2.9 Mit dem Thread verbundene Variablen *
Pfeil 2.9.1 ThreadLocal
Pfeil 2.9.2 InheritableThreadLocal
Pfeil 2.9.3 ThreadLocalRandom als Zufallszahlengenerator
Pfeil 2.9.4 ThreadLocal bei der Performance-Optimierung
Pfeil 2.10 Threads in einer Thread-Gruppe *
Pfeil 2.10.1 Aktive Threads in der Umgebung
Pfeil 2.10.2 Etwas über die aktuelle Thread-Gruppe herausfinden
Pfeil 2.10.3 Threads in einer Thread-Gruppe anlegen
Pfeil 2.10.4 Methoden von Thread und ThreadGroup im Vergleich
Pfeil 2.11 Zeitgesteuerte Abläufe
Pfeil 2.11.1 Die Typen Timer und TimerTask
Pfeil 2.11.2 Job-Scheduler Quartz
Pfeil 2.12 Einen Abbruch der virtuellen Maschine erkennen
Pfeil 2.13 Zum Weiterlesen

Galileo Computing - Zum Seitenanfang

2.5 Synchronisation über Warten und BenachrichtigenZur nächsten Überschrift

Die Synchronisation von Methoden oder Blöcken ist eine einfache Möglichkeit, konkurrierende Zugriffe von der virtuellen Maschine auflösen zu lassen. Obwohl die Umsetzung mit den Locks die Programmierung einfach macht, reicht dies für viele Aufgabenstellungen nicht aus. Wir können zwar Daten in einer synchronisierten Abfolge austauschen, doch gerne möchte ein Thread das Ankommen von Informationen signalisieren, und andere Threads wollen informiert werden, wenn Daten bereitstehen und abgeholt werden können.

Bei der Realisierung der Benachrichtigungen gibt es eine Reihe von Möglichkeiten. Im Folgenden nennen wir die einfachsten:

  • Jedes Objekt besitzt über die Klasse java.lang.Object die Methoden wait() und notify(). Ein Thread, der über den Monitor verfügt, kann die Methoden aufrufen und sich so in einen Wartezustand versetzen oder einen anderen Thread aufwecken. Diese Möglichkeit gibt es seit Java 1.0 (es ist schon ein wenig seltsam, dass Java für die Synchronisation ein eingebautes Schlüsselwort hat, aber die Benachrichtigung über Methoden realisiert).
  • Von einem ReentrantLock – der den Monitor repräsentiert – liefert newCondition() ein Condition-Objekt, das sich über await() und signal() warten und benachrichtigen lässt. Diese Typen gibt es seit Java 5.

Abbildung

Szenarien mit Warten und Benachrichtigen sind oft Produzenten-Konsumenten-Beispiele. Ein Thread liefert Daten, die ein anderer Thread verwenden möchte. Da dieser in keiner kostspieligen Schleife auf die Information warten soll, synchronisieren sich die Partner über ein beiden bekanntes Objekt. Erst wenn der Produzent sein Okay gegeben hat, ergibt es für den Datennutzer Sinn, weiterzuarbeiten; jetzt hat er seine benötigten Daten. So wird keine unnötige Zeit in Warteschleifen vergeudet, und der Prozessor kann die übrige Zeit anderen Threads zuteilen.


Galileo Computing - Zum Seitenanfang

2.5.1 Die Schnittstelle ConditionZur nächsten ÜberschriftZur vorigen Überschrift

Mit einem Lock-Objekt wie ReentrantLock können zwecks Benachrichtigung Condition-Objekte abgeleitet werden. Dazu dient die Methode newCondition():

interface java.util.concurrent.locks.Lock
  • Condition newCondition()
    Liefert ein Condition-Objekt, das mit dem Lock verbunden ist. Mit einem Lock-Objekt können beliebig viele Condition-Objekte gebildet werden.

Warten mit await() und Aufwecken mit signal()

Damit das Warten und Benachrichtigen funktioniert, kommunizieren die Parteien über ein gemeinsames Condition-Objekt, das vom Lock erfragt wird:

Condition condition = lock.newCondition();

In einem fiktiven Szenario soll ein Thread T1 auf ein Signal warten und ein Thread T2 dieses Signal geben. Da nun beide Threads Zugriff auf das gemeinsame Condition-Objekt haben, kann T1 sich mit folgender Anweisung in den Schlaf begeben:

try {
condition.await();
} catch ( InterruptedException e ) {
...
}

Mit dem await() geht der Thread in den Zustand nicht ausführend über. Der Grund für den try-Block ist, dass ein await() durch eine InterruptedException vorzeitig abgebrochen werden kann. Das passiert zum Beispiel, wenn der wartende Thread per interrupt()-Methode einen Hinweis zum Abbruch bekommt.

Die Methode await() bestimmt den ersten Teil des Paares. Der zweite Thread T2 kann nun nach dem Eintreffen einer Bedingung das Signal geben:

condition.signal();

Um das signal() muss es keinen Block geben, der Exceptions auffängt.

Hinweis

Wenn ein Thread ein signal() auslöst und es keinen wartenden Thread gibt, dann verhallt das signal() ungehört. Der Hinweis wird nicht gespeichert, und ein nachfolgendes await() muss mit einem neuen signal() aufgeweckt werden.

Abbildung

Abbildung 2.10: Die Schnittstelle Condition

interface java.util.concurrent.lock.Condition
  • void await() throws InterruptedException
    Wartet auf ein Signal, oder die Methode wird unterbrochen.
  • void signal()
    Weckt einen wartenden Thread auf.

Vor der Condition kommt ein Lock

Auf den ersten Blick scheint es, als ob das Lock-Objekt nur die Aufgabe hat, ein Condition-Objekt herzugeben. Das ist aber noch nicht alles, denn die Methoden await() und auch signal() können nur dann aufgerufen werden, wenn vorher ein lock() den Signal-Block exklusiv sperrt:

lock.lock();
try {
condition.await();
} catch ( InterruptedException e ) {
...
}
finally {
lock.unlock();
}

Doch was passiert ohne Aufruf von lock()? Zwei Zeilen zeigen die Auswirkung:

Listing 2.26: com/tutego/insel/thread/concurrent/AwaitButNoLock.java, main()

Condition condition = new ReentrantLock().newCondition();
condition.await(); // java.lang.IllegalMonitorStateException

Das Ergebnis ist eine java.lang.IllegalMonitorStateException.

Temporäre Lock-Freigabe bei await()

Um auf den Condition-Objekten also await() und signal() aufrufen zu können, ist ein vorangehender Lock nötig. Augenblick mal: Wenn ein await() kommt, hält der Thread doch den Monitor, und kein anderer Thread könnte in einem kritischen Abschnitt, der über das gleiche Lock-Objekt gesperrt ist, signal() aufrufen. Wie ist das möglich? Die Lösung besteht darin, dass await() den Monitor freigibt und den Thread so lange sperrt, bis zum Beispiel von einem anderen Thread das signal() kommt (wenn wir ein Programm mit nur einem Thread haben, dann ergibt natürlich so ein Pärchen keinen Sinn). Kommt das Signal, weckt das den wartenden Thread wieder auf, und er kann am Scheduling wieder teilnehmen. Da also ein anderer Thread prinzipiell in den gleichen Block wie der Wartende hineinlaufen kann, ist das nicht logisch atomar – was es wäre, wenn der Thread komplett einen synchronisierten Block durchliefe, bevor ein anderer Thread den Block betritt.

Mehrere Wartende und signalAll()

Es kann durchaus vorkommen, dass mehrere Threads in einer Warteposition an demselben Objekt sind und aufgeweckt werden wollen. signal() wählt dann aus der Liste der Wartenden einen Thread aus und gibt ihm das Signal. Sollten alle Wartenden einen Hinweis bekommen, lässt sich signalAll() verwenden.

interface java.util.concurrent.lock.Condition
  • void signalAll()
    Weckt alle wartenden Threads auf.

await() mit einer Zeitspanne

Ein await() wartet im schlechtesten Fall bis zum Sankt-Nimmerleins-Tag, wenn es kein signal() gibt. Es gibt jedoch Situationen, in denen wir eine bestimmte Zeit lang warten, aber bei Fehlen der Benachrichtigung weitermachen wollen. Dazu kann dem await() in unterschiedlichen Formen eine Zeit mitgegeben werden.

interface java.util.concurrent.lock.Condition
  • long awaitNanos(long nanosTimeout) throws InterruptedException
    Wartet eine bestimmte Anzahl Nanosekunden auf ein Signal, oder die Methode wird unterbrochen. Die Rückgabe gibt die Wartezeit an.
  • boolean await(long time, TimeUnit unit) throws InterruptedException
  • boolean awaitUntil(Date deadline) throws InterruptedException
    Wartet eine bestimmte Zeit lang auf ein Signal. Kommt das Signal in der Zeit nicht, geht die Methode weiter und liefert true. Kam das Signal oder ein interrupt(), liefert die Methode false.
  • void awaitUninterruptibly()
    Wartet ausschließlich auf ein Signal und lässt sich nicht durch ein interrupt() beenden.

An den Methoden ist schon zu erkennen, dass die Wartezeit einmal relativ (await()) und einmal absolut (awaitUntil()) sein kann (mit den eingebauten Methoden wait() und notify() ist immer nur eine relative Angabe möglich).

Eine IllegalMonitorStateException wird das Ergebnis sein, wenn beim Aufruf einer Condition-Methode das lock() des zugrunde liegenden Lock-Objekts gefehlt hat.

Beispiel

Warte maximal zwei Sekunden auf das Signal über condition1. Wenn es nicht ankommt, versuche signal()/signalAll() von condition2 zu bekommen:

condition1.await( 2, TimeUnit.SECONDS );
condition2.await();

Die Ausnahmebehandlung muss bei einem lauffähigen Beispiel noch hinzugefügt werden.


Galileo Computing - Zum Seitenanfang

2.5.2 It’s Disco-Time *Zur nächsten ÜberschriftZur vorigen Überschrift

Ein kleines Programm soll die Anwendung von Threads kurz demonstrieren. Gegeben sind eine Tanzfläche und Tänzer, die auf die Tanzfläche möchten. Gleichzeitig holen Hütchenspieler immer wieder Tänzer von der Tanzfläche herunter. In die Java-Sprache übersetzt heißt das, dass zwei Threads auf eine gemeinsame Datenbasis, eine Queue, zurückgreifen. Ein Thread produziert unentwegt Tänzer und setzt sie in die Queue. Der Hütchenspieler-Thread nimmt die Tänzer aus der Queue heraus.

Beginnen wir mit einer Klasse Dancefloor, die mit den Methoden in() und out() Tänzer auf die Tanzfläche setzt und sie dort wieder entnimmt. Die Größe der Tanzfläche ist auf 5 beschränkt, und so blockiert die Methode in(), wenn mehr als das definierte Maximum von Tänzern gespeichert werden soll. (Gut, wir hätten die Elemente auch verwerfen können, aber das ist hier keine gute Idee, denn Daten sollen nicht einfach verschwinden. In einigen Szenarien ist aber das Verwerfen von Anfragen die beste Strategie.) Eine zweite Wartesituation haben wir auch noch: Ist die Tanzfläche leer, kann out() keinen Tänzer ausgeben:

Listing 2.27: com/tutego/insel/thread/concurrent/DanceFloorDemo.java, Teil 1

class Dancefloor
{
private final Queue<String> dancers = new LinkedList<String>();
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();

public void in( String name ) throws InterruptedException
{
lock.lock();
try
{
while ( dancers.size() == 5 )
{
System.out.printf( "Tanzfläche ist voll, kann %s nicht aufnehmen!%n", name );
notFull.await();
}

dancers.add( name );
System.out.printf( "%s ist neu auf der Tanzfläche.%n", name );

notEmpty.signalAll();
}
finally {
lock.unlock();
}
}

public String out() throws InterruptedException
{
lock.lock();
try
{
while ( dancers.size() == 0 )
{
System.out.println( "Keiner auf der Tanzfläche!" );
notEmpty.await();
}

String elem = dancers.poll();

notFull.signalAll();

return elem;
}
finally {
lock.unlock();
}
}
}

Als Objektvariable wird eine Warteschlange als Objekt vom Typ Queue deklariert, die die Daten aufnimmt, auf die die Threads dann zurückgreifen. Die erste deklarierte Methode ist in(). Wenn noch Platz in der Warteschlange ist, dann hängt die Methode die Zeichenketten mit dem Tänzernamen an. Ist die Tanzfläche voll, muss gewartet werden, bis jemand einen Tänzer von der Tanzfläche nimmt.

while ( dancers.size() == 5 )
notFull.await();

Es ist typisch für Wartesituationen, dass await() – die synchronisierte Aktion, die ein zusätzliches Kriterium sicherstellt – in einem Schleifenrumpf aufgerufen wird. Denn falls signalAll() einen Thread aus dem await() holt, muss außerdem getestet werden, ob die Bedingung immer noch gilt. Ein einfaches if würde dazu führen, dass bei zwei aufgeweckten Threads beide glauben, die Queue könne jeweils ein Element, also in der Summe zwei, aufnehmen. Doch bei nur einem entnommenen Element – und dem damit verbundenen signalAll() – darf auch nur ein Element wieder hinein. Die Schleife verhindert, dass jeder Geweckte ein Element hineinlegt. Die Bedingung, ob schon maximal viele Produkte in der Datenstruktur stecken, wird im Englischen guard (zu Deutsch etwa Wache) genannt, und die überwachte Aktion (engl. guarded action) Warte wird damit zum sogenannten überwachten Warten (engl. guarded wait).

Bei der Methode out() finden wir das gleiche Muster. Sind in der Warteschlange keine Daten vorhanden, so muss der interessierte Thread warten. Kommt ein Element hinein, kann genau eines herausgenommen werden. Eine einfache Fallunterscheidung könnte bei zwei Wartenden und einem signalAll() vom neuen Element dazu führen, dass beide Threads ein Element entnehmen. Das geht aber nicht, da nur ein Element in der Queue ist.

Die Schleifenbedingung – etwa dancers.size() == 0 – ist das Gegenteil der Bedingung, auf die gewartet werden soll: Anzahl Tänzer ungleich null, dann geht’s weiter.

Unsere Dancefloor-Klasse ist ein Beispiel für eine sogenannte beschränkte blockierende Queue. BlockingQueue ist eine Schnittstelle für blockierende Operationen, und die Klasse ArrayBlockingQueue ist eine solche Warteschlange, die blockiert, wenn keine Daten enthalten sind und ein Maximum erreicht ist. Jetzt haben wir uns eine einfache Variante einer solchen Datenstruktur selbst gebaut!

In der Quellcodedatei folgt zunächst ein Thread, der unermüdlich Tänzer generiert und auf die Tanzfläche schickt:

Listing 2.28: com/tutego/insel/thread/concurrent/DanceFloorDemo.java, Teil 2

public class DancefloorDemo
{
public static void main( String[] args )
{
final Dancefloor dancefloor = new Dancefloor();

new Thread()
{
@Override public void run()
{
try
{
while ( true )
{
dancefloor.in( "Tony Manero " + System.nanoTime() % 100 );

TimeUnit.MILLISECONDS.sleep( new Random().nextInt( 200 ) + 1 );
}
}
catch ( InterruptedException e ) { e.printStackTrace(); }
}
}.start();

Der letzte Teil soll eine Thread-Klasse für Hütchenspieler deklarieren. Ein Hütchenspieler wird dann an der Tanzfläche lauern und Tänzer entnehmen:

Listing 2.29: com/tutego/insel/thread/concurrent/DanceFloorDemo.java, Teil 3

    class Thimblerigger extends Thread
{
final private String name;

Thimblerigger( String name )
{
this.name = name;
}

@Override public void run()
{
try
{
while ( true )
{
System.out.println( name + " nahm " + dancefloor.out()
+ " von der Tanzfläche." );

TimeUnit.MILLISECONDS.sleep( new Random().nextInt( 2000 ) + 1 );
}
}
catch ( InterruptedException e ) { e.printStackTrace(); }
}
}

new Thimblerigger( "Hütchenspieler Heinz" ).start();
new Thimblerigger( "Pronto Salvatore" ).start();
new Thimblerigger( "Flinker Finger Jo" ).start();
}
}

Das Programm startet drei Hütchenspieler, was eine Ausgabe ähnlich dieser ergibt:

Tony Manero 84 ist neu auf der Tanzfläche.
Hütchenspieler Heinz nahm Tony Manero 84 von der Tanzfläche.
Keiner auf der Tanzfläche!
Keiner auf der Tanzfläche!
Tony Manero 4 ist neu auf der Tanzfläche.
Pronto Salvatore nahm Tony Manero 4 von der Tanzfläche.
Keiner auf der Tanzfläche!
Tony Manero 98 ist neu auf der Tanzfläche.
Flinker Finger Jo nahm Tony Manero 98 von der Tanzfläche.
Tony Manero 55 ist neu auf der Tanzfläche.
Tony Manero 62 ist neu auf der Tanzfläche.
Flinker Finger Jo nahm Tony Manero 55 von der Tanzfläche.
Tony Manero 35 ist neu auf der Tanzfläche.
Tony Manero 33 ist neu auf der Tanzfläche.
Tony Manero 38 ist neu auf der Tanzfläche.
Tony Manero 79 ist neu auf der Tanzfläche.
Hütchenspieler Heinz nahm Tony Manero 62 von der Tanzfläche.
Tony Manero 70 ist neu auf der Tanzfläche.
Hütchenspieler Heinz nahm Tony Manero 35 von der Tanzfläche.
Tony Manero 35 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 60 nicht aufnehmen!
Flinker Finger Jo nahm Tony Manero 33 von der Tanzfläche.
Tony Manero 60 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 33 nicht aufnehmen!
Flinker Finger Jo nahm Tony Manero 38 von der Tanzfläche.
Tony Manero 33 ist neu auf der Tanzfläche.
Pronto Salvatore nahm Tony Manero 79 von der Tanzfläche.
Tony Manero 98 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 65 nicht aufnehmen!
Flinker Finger Jo nahm Tony Manero 70 von der Tanzfläche.
Tony Manero 65 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 36 nicht aufnehmen!
Hütchenspieler Heinz nahm Tony Manero 35 von der Tanzfläche.
Tony Manero 36 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 82 nicht aufnehmen!
Pronto Salvatore nahm Tony Manero 60 von der Tanzfläche.
Tony Manero 82 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 32 nicht aufnehmen!
Hütchenspieler Heinz nahm Tony Manero 33 von der Tanzfläche.
Tony Manero 32 ist neu auf der Tanzfläche.
Pronto Salvatore nahm Tony Manero 98 von der Tanzfläche.
Tony Manero 44 ist neu auf der Tanzfläche.
Tanzfläche ist voll, kann Tony Manero 76 nicht aufnehmen!
... bis in die Unendlichkeit

Galileo Computing - Zum Seitenanfang

2.5.3 Warten mit wait() und Aufwecken mit notify() *Zur nächsten ÜberschriftZur vorigen Überschrift

Nachdem im vorigen Abschnitt der Weg mit den Klassen und Schnittstellen aus Java 5 beschritten wurde, wollen wir uns abschließend mit den Möglichkeiten beschäftigen, die Java seit der Version 1.0 mitbringt.

Nehmen wir wieder zwei Threads an. Sie sollen sich am Objekt o synchronisieren – die Methoden wait() und notify() sind nur mit dem entsprechenden Monitor gültig, und den besitzt das Programmstück, wenn es sich in einem synchronisierten Block aufhält. Thread T1 soll auf Daten warten, die Thread T2 liefert. T1 führt dann etwa den folgenden Programmcode aus:

synchronized( o )
{
try {
o.wait();
// Habe gewartet, kann jetzt loslegen.
} catch ( InterruptedException e ) {
...
}
}

Wenn der zweite Thread den Monitor des Objekts o bekommt, kann er den wartenden Thread aufwecken. Er bekommt den Monitor durch das Synchronisieren der Methode, was ja bei Objektmethoden synchronized(this) entspricht. T2 gibt das Signal mit notify():

synchronized( o )
{
// Habe etwas gemacht und informiere jetzt meinen Wartenden.
o.notify();
}

Um notify() muss es keinen Block geben, der Exceptions auffängt. Wenn ein Thread ein notify() auslöst und es keinen wartenden Thread gibt, dann verpufft es.

class java.lang.Object
  • void wait() throws InterruptedException
    Der aktuelle Thread wartet an dem aufrufenden Objekt darauf, dass er nach einem notify()/notifyAll() weiterarbeiten kann. Der aktive Thread muss natürlich den Monitor des Objekts belegt haben. Andernfalls kommt es zu einer IllegalMonitorStateException.
  • void wait(long timeout) throws InterruptedException
    Wartet auf ein notify()/notifyAll() eine gegebene Anzahl von Millisekunden. Nach Ablauf dieser Zeit geht es ohne Fehler weiter.
  • void wait(long timeout, int nanos) throws InterruptedException
    Wartet auf ein notify()/notifyAll() – angenähert 1.000.000 * timeout + nanos Nanosekunden.
  • void notify()
    Weckt einen beliebigen Thread auf, der an diesem Objekt wartet.
  • void notifyAll()
    Benachrichtigt alle Threads, die auf dieses Objekt warten.

Für die Variante wait(long)/wait(long,int) mit der Zeiteinheit bietet TimeUnit eine Alternative.

enum java.util.concurrent.TimeUnit
extends Enum<TimeUnit>
  • void timedWait(Object obj, long timeout) throws InterruptedException
    Berechnet aus der gewählten TimeUnit und dem timeout die Millisekunden (ms) und Nanosekunden (ns) und führt obj.wait(ms, ns) aus.

Ein wait() kann mit einer InterruptedException vorzeitig abbrechen, wenn der wartende Thread per interrupt()-Methode unterbrochen wird. Die Tatsache, dass wait() temporär den Lock freigibt, was für uns mit synchronized aber nicht möglich ist, spricht dafür, dass etwas wie wait() nativ implementiert werden muss.


Galileo Computing - Zum Seitenanfang

2.5.4 Falls der Lock fehlt: IllegalMonitorStateException *Zur vorigen Überschrift

Wenn wait() oder notify() aufgerufen werden, uns aber der entsprechende Lock für das Objekt fehlt, kommt es zum Laufzeitfehler IllegalMonitorStateException, wie wir es schon bei Condition und dem fehlenden lock() vom Lock gesehen haben.

Was wird bei folgendem Programm passieren?

Listing 2.30: com/tutego/insel/thread/NotOwner.java, main()

package com.tutego.insel.thread;

class NotOwner
{
public static void main( String[] args ) throws InterruptedException
{
new NotOwner().wait();
}
}

Der Compiler kann das Programm übersetzen, doch zur Laufzeit wird es zu einem Fehler kommen:

java.lang.IllegalMonitorStateException: current thread not owner
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:426)
at NotOwner.main(NotOwner.java:5)
Exception in thread "main"

Der Fehler zeigt an, dass der aktuelle ausführende Thread (current thread) nicht den nötigen Lock besitzt, um wait() auszuführen. Das Problem ist hier mit einem synchronized-Block (oder einer synchronized-Methode) zu lösen. Um den Fehler zu beheben, setzen wir:

NotOwner o = new NotOwner();
synchronized( o )
{
o.wait();
}

Dies zeigt, dass das Objekt o, das den Lock besitzt, für ein wait() »bereit« sein muss. In die richtige Stimmung wird es nur mit synchronized gebracht:

synchronized( NotOwner.class )
{
new NotOwner().wait();
}

Doch natürlich könnten wir auch am Klassenobjekt synchronisieren:

synchronized( NotOwner.class )
{
NotOwner.class.wait();
}
Hinweis

Die Ähnlichkeit zwischen Lock auf der einen Seite und einem synchronisierten Block bzw. einer synchronisierten Methode auf der anderen und den Methoden wait() und notify() bei Object und den analogen Methoden await() und signal() bei den Condition-Objekten ist nicht zu übersehen. Auch der Fehler beim Fehlen des Monitors ist der gleiche: Ein Aufruf der Methoden await()/wait() und notify()/signal() führt zu einer IllegalMonitorStateException. Es muss also erst ein synchronisierter Block für den Monitor her oder ein Aufruf lock() auf dem Condition zugrunde liegenden Lock-Objekt.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.







<< zurück
  Zum Katalog
Zum Katalog: Java 7 – Mehr als eine Insel
Java 7 – Mehr als eine Insel
Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java und XML






 Java und XML


Zum Katalog: Einstieg in Eclipse 3.7






 Einstieg in
 Eclipse 3.7


Zum Katalog: Android 3






 Android 3


Zum Katalog: NetBeans Platform 7






 NetBeans Platform 7


Zum Katalog: Java ist auch eine Insel






 Java ist
 auch eine Insel


Zum Katalog: Apps entwickeln für Android 4






 Apps entwickeln
 für Android 4


Zum Katalog: Java 7






 Java 7


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2012
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de