Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Angewandte Objektorientierung
8 Exceptions
9 Generics, innere Klassen
10 Die Klassenbibliothek
11 Threads und nebenläufige Programmierung
12 Datenstrukturen und Algorithmen
13 Raum und Zeit
14 Dateien und Datenströme
15 Die eXtensible Markup Language (XML)
16 Grafische Oberflächen mit Swing
17 Grafikprogrammierung
18 Netzwerkprogrammierung
19 Verteilte Programmierung mit RMI und Web–Services
20 JavaServer Pages und Servlets
21 Applets
22 Midlets und die Java ME
23 Datenbankmanagement mit JDBC
24 Reflection und Annotationen
25 Logging und Monitoring
26 Sicherheitskonzepte
27 Java Native Interface (JNI)
28 Dienstprogramme für die Java-Umgebung
Stichwort

Download:
- ZIP, ca. 14,1 MB
Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel (8. Auflage) von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel (8. Auflage)

Java ist auch eine Insel (8. Aufl.)
8., aktual. Auflage, geb., mit DVD
1.475 S., 49,90 Euro
Galileo Computing
ISBN 978-3-8362-1371-4
Pfeil 11 Threads und nebenläufige Programmierung
Pfeil 11.1 Nebenläufigkeit
Pfeil 11.1.1 Threads und Prozesse
Pfeil 11.1.2 Wie parallele Programme die Geschwindigkeit steigern können
Pfeil 11.1.3 Was Java für Nebenläufigkeit alles bietet
Pfeil 11.2 Threads erzeugen
Pfeil 11.2.1 Threads über die Schnittstelle Runnable implementieren
Pfeil 11.2.2 Thread mit Runnable starten
Pfeil 11.2.3 Der Name eines Threads
Pfeil 11.2.4 Die Klasse Thread erweitern
Pfeil 11.2.5 Wer bin ich?
Pfeil 11.3 Die Zustände eines Threads
Pfeil 11.3.1 Threads schlafen
Pfeil 11.3.2 Mit yield() auf Rechenzeit verzichten
Pfeil 11.3.3 Das Ende eines Threads
Pfeil 11.3.4 UncaughtExceptionHandler für unbehandelte Ausnahmen
Pfeil 11.3.5 Einen Thread höflich mit Interrupt beenden
Pfeil 11.3.6 Der stop() von außen und die Rettung mit ThreadDeath
Pfeil 11.3.7 Ein Rendezvous mit join()
Pfeil 11.3.8 Barrier und Austausch mit Exchanger
Pfeil 11.3.9 Arbeit niederlegen und wieder aufnehmen
Pfeil 11.3.10 Priorität
Pfeil 11.3.11 Der Thread als Dämon
Pfeil 11.4 Der Ausführer (Executor) kommt
Pfeil 11.4.1 Die Schnittstelle Executor
Pfeil 11.4.2 Die Thread-Pools
Pfeil 11.4.3 Threads mit Rückgabe über Callable
Pfeil 11.4.4 Mehrere Callable abarbeiten
Pfeil 11.4.5 Mit ScheduledExecutorService wiederholende Ausgaben und Zeitsteuerungen
Pfeil 11.5 Synchronisation über kritische Abschnitte
Pfeil 11.5.1 Gemeinsam genutzte Daten
Pfeil 11.5.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
Pfeil 11.5.3 Punkte parallel initialisieren
Pfeil 11.5.4 i++ sieht atomar aus, ist es aber nicht
Pfeil 11.5.5 Kritische Abschnitte schützen
Pfeil 11.5.6 Schützen mit ReentrantLock
Pfeil 11.5.7 Synchronisieren mit synchronized
Pfeil 11.5.8 Synchronized-Methoden der Klasse StringBuffer
Pfeil 11.5.9 Mit synchronized synchronisierte Blöcke
Pfeil 11.5.10 Dann machen wir doch gleich alles synchronisiert!
Pfeil 11.5.11 Lock-Freigabe im Fall von Exceptions
Pfeil 11.5.12 Mit synchronized nachträglich synchronisieren
Pfeil 11.5.13 Monitore sind reentrant – gut für die Geschwindigkeit
Pfeil 11.5.14 Synchronisierte Methodenaufrufe zusammenfassen
Pfeil 11.5.15 Deadlocks
Pfeil 11.6 Synchronisation über Warten und Benachrichtigen
Pfeil 11.6.1 Die Schnittstelle Condition
Pfeil 11.6.2 It’s Disco-Time
Pfeil 11.6.3 Warten mit wait() und Aufwecken mit notify()
Pfeil 11.6.4 Falls der Lock fehlt: IllegalMonitorStateException
Pfeil 11.6.5 Semaphor
Pfeil 11.7 Atomare Operationen und frische Werte mit volatile
Pfeil 11.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
Pfeil 11.7.2 Das Paket java.util.concurrent.atomic
Pfeil 11.8 Mit dem Thread verbundene Variablen
Pfeil 11.8.1 ThreadLocal
Pfeil 11.8.2 InheritableThreadLocal
Pfeil 11.9 Gruppen von Threads in einer Thread-Gruppe
Pfeil 11.9.1 Aktive Threads in der Umgebung
Pfeil 11.9.2 Etwas über die aktuelle Thread-Gruppe herausfinden
Pfeil 11.9.3 Threads in einer Thread-Gruppe anlegen
Pfeil 11.9.4 Methoden von Thread und ThreadGroup im Vergleich
Pfeil 11.10 Zeitgesteuerte Abläufe
Pfeil 11.10.1 Die Klassen Timer und TimerTask
Pfeil 11.10.2 Job-Scheduler Quartz
Pfeil 11.11 Einen Abbruch der virtuellen Maschine erkennen
Pfeil 11.12 Zum Weiterlesen


Galileo Computing - Zum Seitenanfang

11.3 Die Zustände eines Threads Zur nächsten ÜberschriftZur vorigen Überschrift

Bei einem Thread-Exemplar können wir einige Zustände feststellen:

1. Nicht erzeugt. Der Lebenslauf eines Thread-Objekts beginnt mit new, doch befindet er sich damit noch nicht im Zustand ausführend.
2. Laufend (vom Scheduler berücksichtigt) und nicht laufend (vom Scheduler nicht berücksichtigt). Durch start() gelangt der Thread in den Zustand »ausführbar« beziehungsweise »laufend«. Der Zustand kann sich ändern, wenn ein anderer Thread zur Ausführung gelangt und dann dem aktuellen Thread den Prozessor entzieht. Der vormals laufende Thread kommt in den Zustand nicht laufend, bis der Scheduler ihm wieder Rechenzeit zuordnet.
3. Wartend. Dieser Zustand wird mittels spezieller Synchronisationstechniken oder Ein-/Ausgabefunktionen erreicht – der Thread verweilt in einem Wartezustand.
4. Beendet. Nachdem die Aktivität des Thread-Objekts beendet wurde, kann es nicht mehr aktiviert werden und ist tot, also beendet.

Zustand über Thread.State

In welchem Zustand ein Thread gerade ist, zeigt die Methode getState(). Sie liefert ein Objekt vom Typ der Aufzählung Thread.State – die einzige im Paket java.lang –, die definiert:


NEW

Neuer Thread, noch nicht gestartet.

RUNNABLE

Läuft in der JVM.

BLOCKED

Wartet auf einen MonitorLock, wenn er etwa einen synchronized-Block betreten möchte.

WAITING

Wartet etwa auf ein notify().

TIMED_WAITING

Wartet etwa in einem sleep().

TERMINATED

Ausführung ist beendet.


Zudem lässt sich die Methode isAlive() verwenden, die erfragt, ob der Thread gestartet wurde, aber noch nicht tot ist.


Galileo Computing - Zum Seitenanfang

11.3.1 Threads schlafen Zur nächsten ÜberschriftZur vorigen Überschrift

Manchmal ist es notwendig, einen Thread eine bestimmte Zeit lang anzuhalten. Dazu lassen sich Methoden zweier Klassen nutzen:

  • Die überladene Funktion Thread.sleep(). Etwas erstaunlich ist sicherlich, dass sie keine Objektmethode von einem Thread-Objekt ist, sondern eine statische Funktion. Ein Grund wäre, dass dadurch verhindert wird, externe Threads zu beeinflussen. Es ist nicht möglich, einen fremden Thread, über dessen Referenz wir verfügen, einfach einige Sekunden lang schlafen zu legen und ihn so von der Ausführung abzuhalten.
  • Die Objektmethode sleep() auf einem TimeUnit-Objekt. Auch sie bezieht sich immer auf den ausführenden Thread. Der Vorteil gegenüber sleep() ist, dass hier die Zeiteinheiten besser sichtbar sind.

Beispiel Der ausführende Thread soll zwei Sekunden lang schlafen. Einmal mit Thread. sleep():

try { 
  Thread.sleep( 2000 ); 
} catch ( InterruptedException e ) { }

Dann mit TimeUnit:

try {
  TimeUnit.SECONDS.sleep( 2 ); 
} catch ( InterruptedException e ) { }

Die Unterbrechung sitzt in einem zwingenden try-catch-Block, da eine InterruptedException ausgelöst wird, wenn der Thread unterbrochen wird – etwa durch interrupt() –, und diese keine RuntimeException ist.

Praktisch wird das Erweitern der Klasse Thread bei inneren anonymen Klassen. Die folgende Anweisung gibt nach zwei Sekunden Schlafzeit eine Meldung auf dem Bildschirm aus.

Listing 11.5 com/tutego/insel/thread/SleepInInnerClass.java, main()

new Thread() { 
  @Override public void run() { 
    try { 
      Thread.sleep( 2000 ); 
      System.out.println( "Zeit ist um." ); 
    } catch ( InterruptedException e ) { e.printStackTrace(); } 
  } 
}.start();

Da new Thread(){...} ein Exemplar der anonymen Unterklasse ergibt, lässt die auf dem Ausdruck aufgerufene Objektmethode start() den Thread gleich loslaufen. Aufgaben dieser Art lösen auch die Timer gut.


class java.lang.Thread 
implements Runnable

  • static void sleep( long millis ) throws InterruptedException
    Der aktuell ausgeführte Thread wird mindestens millis Millisekunden eingeschläfert. Unterbricht ein anderer Thread den schlafenden, wird vorzeitig eine InterruptedException ausgelöst.
  • static void sleep( long millis, int nanos ) throws InterruptedException
    Der aktuell ausgeführte Thread wird mindestens millis Millisekunden und zusätzlich nanos Nanosekunden eingeschläfert. Im Gegensatz zu sleep(long) wird bei einer negativen Millisekundenanzahl eine IllegalArgumentException ausgelöst; auch wird diese Exception ausgelöst, wenn die Nanosekundenanzahl nicht zwischen 0 und 999.999 liegt.

enum java.util.concurrent.TimeUnit 
extends Enum<TimeUnit> 
implements Serializable, Comparable<TimeUnit>

  • NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
    Aufzählungselemente von TimeUnit.
  • void sleep( long timeout ) throws InterruptedException
    Führt ein Thread.sleep() für die Zeiteinheit aus.

Eine überladene Funktion Thread.sleep(TimeUnit, long) wäre praktisch, gibt es aber nicht!


Galileo Computing - Zum Seitenanfang

11.3.2 Mit yield() auf Rechenzeit verzichten Zur nächsten ÜberschriftZur vorigen Überschrift

Neben sleep() gibt es eine weitere Methode, um kooperative Threads zu programmieren: die Methode yield(). Sie funktioniert etwas anders als sleep(), da hier nicht nach Ablauf der genannten Millisekunden zum Thread zurückgekehrt wird, sondern yield() den Thread bezüglich seiner Priorität wieder in die Thread-Warteschlange des Systems einordnet. Einfach ausgedrückt, sagt yield() der Thread-Verwaltung: »Ich setze diese Runde aus und mache weiter, wenn ich das nächste Mal dran bin.«


class java.lang.Thread 
implements Runnable

  • static void yield()
    Der laufende Thread gibt freiwillig seine Rechenzeit ab. Die Methode ist für Implementierungen der JVM nicht verbindlich.

Galileo Computing - Zum Seitenanfang

11.3.3 Das Ende eines Threads Zur nächsten ÜberschriftZur vorigen Überschrift

Es gibt Threads, die dauernd laufen, weil sie zum Beispiel Serverfunktionen implementieren. Andere Threads führen einmalig eine Operation aus und sind danach beendet. Allgemein ist ein Thread beendet, wenn eine der folgenden Bedingungen zutrifft:

  • Die run()-Methode wurde ohne Fehler beendet. Wenn wir eine Endlosschleife programmieren, würde diese potenziell einen nie endenden Thread bilden.
  • In der run()-Methode tritt eine RuntimeException auf, die die Methode beendet. Das beendet weder die anderen Threads noch die JVM als Ganzes.
  • Der Thread wurde von außen abgebrochen. Dazu dient die prinzipbedingt problematische Methode stop(), von deren Verwendung abgeraten wird und die auch veraltet ist.
  • Die virtuelle Maschine wird beendet und nimmt alle Threads mit ins Grab.

Wenn der Thread einen Fehler melden soll

Da ein Thread nebenläufig arbeitet, kann die run()-Methode synchron schlecht Exceptions melden oder einen Rückgabewert liefern. Wer sollte auch an welcher Stelle darauf hören? Eine Lösung für das Problem ist ein Listener, der sich beim Thread anmeldet und darüber informiert wird, ob der Thread seine Arbeit machen konnte oder nicht. Eine andere Lösung gibt Callable, mit dem ein spezieller Fehlercode zurückgegeben oder eine Exception angezeigt werden kann. Speziell für ungeprüfte Ausnahmen kann ein UncaughtExceptionHandler weiterhelfen.


Galileo Computing - Zum Seitenanfang

11.3.4 UncaughtExceptionHandler für unbehandelte Ausnahmen Zur nächsten ÜberschriftZur vorigen Überschrift

Einer der Gründe für das Ende eines Threads ist eine unbehandelte Ausnahme, etwa von einer nicht aufgefangenen RuntimeException. Um in diesem Fall einen kontrollierten Abgang zu ermöglichen, lässt sich an den Thread ein UncaughtExceptionHandler hängen, der immer dann benachrichtigt wird, wenn der Thread wegen einer nicht behandelten Ausnahme endet.

UncaughtExceptionHandler ist eine in Thread definierte innere Schnittstelle, die eine Operation void uncaughtException(Thread t, Throwable e) vorschreibt. Eine Implementierung der Schnittstelle lässt sich entweder einem individuellen Thread oder allen Threads anhängen, sodass im Fall des Abbruchs durch unbehandelte Ausnahmen die JVM die Methode uncaughtException() aufruft. Auf diese Weise kann die Applikation im letzten Atemzug noch den Fehler loggen, den die JVM über das Throwable e übergibt.


class java.lang.Thread 
implements Runnable

  • void setUncaughtExceptionHandler( Thread.UncaughtExceptionHandler eh )
    Setzt den UncaughtExceptionHandler für den Thread.
  • Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
    Liefert den aktuellen UncaughtExceptionHandler.
  • static void setDefaultUncaughtExceptionHandler( Thread.UncaughtExceptionHandler eh ) Setzt den UncaughtExceptionHandler für alle Threads.
  • static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
    Liefert den zugewiesenen UncaughtExceptionHandler aller Threads.

Ein mit setUncaughtExceptionHandler() lokal gesetzter UncaughtExceptionHandler überschreibt den Eintrag für den setDefaultUncaughtExceptionHandler(). Zwischen dem mit dem Thread assoziierten Handler und dem globalen gibt es noch einen Handler-Typ für Thread-Gruppen, der jedoch seltener verwendet wird. (Vor Java 5 war er jedoch die einzige Möglichkeit, das Ende zu erkennen.)


Galileo Computing - Zum Seitenanfang

11.3.5 Einen Thread höflich mit Interrupt beenden Zur nächsten ÜberschriftZur vorigen Überschrift

Der Thread ist in der Regel zu Ende, wenn die run()-Methode ordentlich bis zum Ende ausgeführt wurde. Enthält eine run()-Methode jedoch eine Endlosschleife – wie etwa bei einem Server, der auf eingehende Anfragen wartet –, so muss der Thread von außen zur Kapitulation gezwungen werden. Die naheliegende Möglichkeit, mit der Thread-Methode stop() einen Thread abzuwürgen, wollen wir im Abschnitt 11.3.6 diskutieren.

Wenn wir den Thread schon nicht von außen beenden wollen, können wir ihn bitten, seine Arbeit aufzugeben. Periodisch müsste er dann nur überprüfen, ob jemand von außen den Abbruchswunsch geäußert hat.

Die Methoden interrupt() und isInterrupted()

Die Methode interrupt() setzt von außen in einem Thread-Objekt ein internes Flag, das dann in der run()-Methode durch isInterrupted() periodisch abgefragt werden kann.

Das folgende Programm soll jede halbe Sekunde eine Meldung auf dem Bildschirm ausgeben. Nach zwei Sekunden wird der Unterbrechungswunsch mit interrupt() gemeldet. Auf dieses Signal achtet die sonst unendlich laufende Schleife und bricht ab.

Listing 11.6 com/tutego/insel/thread/ThreadusInterruptus.java, main()

Thread t = new Thread() 
{ 
  @Override 
  public void run() 
  { 
    System.out.println( "Es gibt ein Leben vor dem Tod.  " ); 
 
    while ( ! isInterrupted() ) 
    { 
      System.out.println( "Und er läuft und er läuft und er läuft" ); 
 
      try 
      { 
       Thread.sleep( 500 ); 
      } 
      catch ( InterruptedException e ) 
      { 
       interrupt(); 
       System.out.println( "Unterbrechung in sleep()" ); 
      } 
    } 
 
    System.out.println( "Das Ende" ); 
  } 
}; 
t.start(); 
Thread.sleep( 2000 ); 
t.interrupt();

Die Ausgabe zeigt hübsch die Ablaufsequenz:

Es gibt ein Leben vor dem Tod. 
Und er läuft und er läuft und er läuft 
Und er läuft und er läuft und er läuft 
Und er läuft und er läuft und er läuft 
Und er läuft und er läuft und er läuft 
Unterbrechung in sleep() 
Das Ende

Die run()-Methode im Thread ist so implementiert, dass die Schleife genau dann verlassen wird, wenn isInterrupted() den Wert true ergibt, also von außen die interrupt()-Methode für dieses Thread-Exemplar aufgerufen wurde. Genau dies geschieht in der main()-Methode. Auf den ersten Blick ist das Programm leicht verständlich, doch vermutlich erzeugt das interrupt() im catch-Block die Aufmerksamkeit. Stünde diese Zeile dort nicht, würde das Programm aller Wahrscheinlichkeit nach nicht funktionieren. Das Geheimnis ist folgendes: Wenn die Ausgabe nur jede halbe Sekunde stattfindet, befindet sich der Thread fast die gesamte Zeit über in der Schlafmethode sleep(). Also wird vermutlich der interrupt() den Thread gerade beim Schlafen stören. Genau dann wird sleep() durch InterruptedException unterbrochen, und der catch-Behandler fängt die Ausnahme ein. Jetzt passiert aber etwas Unerwartetes: Durch die Unterbrechung wird das interne Flag zurückgesetzt, sodass isInterrupted() meint, die Unterbrechung habe gar nicht stattgefunden. Daher muss interrupt() erneut aufgerufen werden, da das Abbruch-Flag neu gesetzt werden muss und isInterrupted() das Ende bestimmen kann.

Wenn wir mit der Objektmethode isInterrupted() arbeiten, müssen wir beachten, dass neben sleep() auch die Methoden join() und wait() durch die InterruptedException das Flag löschen.


Hinweis Die Methoden sleep(), wait() und join() lösen alle eine InterruptedException aus, wenn sie durch die Methode interrupt() unterbrochen werden. Das heißt: interrupt() beendet diese Methoden mit der Ausnahme.


Zusammenfassung: interrupted(), isInterrupted() und interrupt()

Die Methodennamen sind verwirrend gewählt, sodass wir die Aufgaben noch einmal zusammenfassen wollen: Die Objektmethode interrupt() setzt in einem (anderen) Thread-Objekt ein Flag, dass es einen Antrag gab, den Thread zu beenden. Sie beendet aber den Thread nicht, obwohl es der Methodenname nahelegt. Dieses Flag lässt sich mit der Objektmethode isInterrupted() abfragen. In der Regel wird dies innerhalb einer Schleife geschehen, die darüber bestimmt, ob die Aktivität des Threads fortgesetzt werden soll. Die statische Methode interrupted() ist zwar auch eine Anfragemethode und testet das entsprechende Flag des aktuell laufenden Threads, wie Thread.currentThread().isInterrupted(), aber zusätzlich löscht es den Interrupt-Status auch, was isInterrupted() nicht tut. Zwei aufeinanderfolgende Aufrufe von interrupted() führen daher zu einem false, es sei denn, in der Zwischenzeit erfolgt eine weitere Unterbrechung.


Galileo Computing - Zum Seitenanfang

11.3.6 Der stop() von außen und die Rettung mit ThreadDeath Zur nächsten ÜberschriftZur vorigen Überschrift

Wenn ein Thread nicht auf interrupt() hört, aber aus irgendwelchen Gründen dringend beendet werden muss, müssen wir wohl oder übel die veraltete Methode stop() einsetzen.

Eclipse-Icon Dass die Methode stop() veraltet ist, zeigen eine unterschlängelte Linie und ein Symbol am linken Rand an. Steht der Cursor auf der problematischen Zeile, zeigt eine Fehlermeldung ebenfalls das Problem.

deprecated gibt uns schon einen guten Hinweis darauf, stop() besser nicht zu benutzen. (Leider gibt es hier, im Gegensatz zu den meisten anderen veralteten Methoden, keinen einfachen, empfohlenen Ersatz.) Überschreiben können wir stop() auch nicht, da es final ist. Wenn wir einen Thread von außen beenden, geben wir ihm keine Chance mehr, seinen Zustand konsistent zu verlassen. Zudem kann die Unterbrechung an beliebiger Stelle erfolgen, sodass angeforderte Ressourcen frei in der Luft hängen können.


class java.lang.Thread 
implements Runnable

  • final void stop()
    Wurde der Thread gar nicht gestartet oder ist er bereits abgearbeitet beziehungsweise beendet, kehrt die Methode sofort zurück. Andernfalls wird über checkAccess() geprüft, ob wir überhaupt das Recht haben, den Thread abzuwürgen. Dann wird der Thread beendet, egal was er zuvor unternommen hat; jetzt kann er nur noch sein Testament in Form eines ThreadDeath-Objekts als Exception anzeigen.

Das ThreadDeath-Objekt

So unmöglich ist das Reagieren auf ein stop() auch nicht. Immer dann, wenn ein Thread mit stop() zum Ende kommen soll, löst die JVM eine ThreadDeath-Ausnahme aus, die letztendlich den Thread beendet. ThreadDeath ist eine Unterklasse von Error, das wiederum von Throwable abgeleitet ist, sodass ThreadDeath mit einem try-catch-Block abgefangen werden kann. Die Java-Entwickler haben ThreadDeath nicht zu einer Unterklasse von Exception gemacht, weil sie nicht wollten, dass ThreadDeath bei einer allgemeinen Exception-Behandlung über catch(Exception e) abgefangen wird. (Dass wir die Klasse überhaupt nutzen können, ist einem Fehler von Sun zuzuschreiben. Die Klasse sollte eigentlich nicht sichtbar sein.)

Wenn wir ThreadDeath auffangen, können wir noch auf den Tod reagieren und Aufräumarbeiten erlauben. Wir sollten aber nicht vergessen, anschließend das aufgefangene ThreadDeath-Objekt wieder auszulösen, weil der Thread sonst nicht beendet wird.

Listing 11.7 com/tutego/insel/thread/ThreadStopRecovery.java, main()

Thread t = new Thread() 
{ 
  @Override public void run() 
  { 
    try 
    { 
      while ( true ) System.out.println( "I Like To Move It." ); 
    } 
    catch ( ThreadDeath td ) 
    { 
      System.out.println( "Das Leben ist nicht tot zu kriegen." ); 
      throw td; 
    } 
  } 
}; 
t.start(); 
try { Thread.sleep( 1 ); } catch ( Exception e ) { } 
t.stop();

ThreadDeath bietet eine extravagante Möglichkeit, um das aktuell laufende Programm zu beenden: throw new ThreadDeath(). Die Anweisung System.exit() ist aber weniger aufsehenerregend.


Galileo Computing - Zum Seitenanfang

11.3.7 Ein Rendezvous mit join() Zur nächsten ÜberschriftZur vorigen Überschrift

Wollen wir Aufgaben auf mehrere Threads verteilen, kommt der Zeitpunkt, an dem die Ergebnisse eingesammelt werden. Die Resultate können allerdings erst dann zusammengebracht werden, wenn alle Threads mit ihrer Ausführung fertig sind. Da sie sich zu einem bestimmten Zeitpunkt treffen, heißt das auch Rendezvous.

Zum Warten gibt es mehrere Strategien. Zunächst lässt sich mit Callable arbeiten, um dann mit get() synchron auf das Ende zu warten. Arbeiten wir mit Runnable, so kann ein Thread keine direkten Ergebnisse wie eine Methode nach außen geben, weil die run()-Methode den Ergebnistyp void hat. Da ein nebenläufiger Thread zudem asynchron arbeitet, wissen wir nicht einmal, wann wir das Ergebnis erwarten können.

Die Übertragung von Werten ist kein Problem. Hier können Klassenvariablen und auch Objektvariablen helfen, denn über sie können wir kommunizieren. Jetzt fehlt nur noch, dass wir auf das Ende der Aktivität eines Threads warten können. Das funktioniert mit der Methode join().

In unserem folgenden Beispiel legt ein Thread t in der Variable result ein Ergebnis ab. Wir können die Auswirkungen von join() sehen, wenn wir die auskommentierte Zeile hineinnehmen.

Listing 11.8 com/tutego/insel/thread/JoinTheThread.java

package com.tutego.insel.thread; 
 
class JoinTheThread 
{ 
  static class JoinerThread extends Thread 
  { 
    public int result; 
 
    @Override public void run() 
    { 
      result = 1; 
    } 
  } 
 
  public static void main( String[] args ) throws InterruptedException 
  { 
    JoinerThread t = new JoinerThread(); 
    t.start(); 
//    t.join(); 
    System.out.println( t.result ); 
  } 
}

Ohne den Aufruf von join() wird als Ergebnis 0 ausgegeben, denn das Starten des Threads kostet etwas Zeit. In dieser Zeit geben wir aber die automatisch auf 0 initialisierte Klassenvariable aus. Nehmen wir join() hinein, wird die run()-Methode zu Ende ausgeführt, und der Thread setzt die Variable result auf 1. Das sehen wir dann auf dem Bildschirm.


class java.lang.Thread 
implements Runnable

  • final void join() throws InterruptedException
    Der aktuell ausgeführte Thread wartet auf den Thread, für den die Methode aufgerufen wird, bis dieser beendet ist.
  • final void join( long millis ) throws InterruptedException
    Wie join(), doch wartet diese Variante höchstens millis Millisekunden. Wurde der Thread bis dahin nicht vollständig beendet, fährt das Programm fort. Auf diese Weise kann versucht werden, innerhalb einer bestimmten Zeitspanne auf den Thread zu warten, sonst aber weiterzumachen. Ist millis gleich 0, so hat dies die gleiche Wirkung wie join().
  • final void join ( long millis, int nanos ) throws InterruptedException
    Wie join(long), jedoch mit potenziell genauerer Angabe der maximalen Wartezeit.

Nach einem thread.join(long) ist mitunter die thread.isAlive()-Methode nützlich, denn sie sagt aus, ob thread noch aktiv arbeitet oder beendet ist.

Warten auf den Langsamsten

Große Probleme lassen sich in mehrere Teile zerlegen, und jedes Teilproblem kann dann von einem Thread gelöst werden. Dies ist insbesondere bei Mehrprozessorsystemen eine lohnenswerte Investition. Zum Schluss müssen wir nur noch darauf warten, dass die Threads zum Ende gekommen sind, und das Ergebnis einsammeln. Dazu eignet sich join() gut.


Beispiel Zwei Threads arbeiten an einem Problem. Anschließend wird gewartet, bis beide ihre Aufgabe erledigt haben. Dann könnte etwa ein anderer Thread die von a und b verwendeten Ressourcen wieder nutzen.

Thread a = new AThread(); 
Thread b = new BThread(); 
a.start(); 
b.start(); 
a.join(); 
b.join();

Es ist unerheblich, wessen join() wir zuerst aufrufen, da wir ohnehin auf den langsamsten Thread warten müssen. Wenn ein Thread schon beendet ist, kehrt join() sofort zurück.

Eine andere Lösung für zusammenlaufende Threads besteht darin, diese in einer Thread-Gruppe zusammenzufassen. Dann können sie zusammen behandelt werden, sodass nur das Ende der Thread-Gruppe beobachtet wird.


Galileo Computing - Zum Seitenanfang

11.3.8 Barrier und Austausch mit Exchanger Zur nächsten ÜberschriftZur vorigen Überschrift

Der Punkt, an dem alle Threads zusammenkommen und sich die Ergebnisse zusammenlegen lassen, heißt auf Englisch barrier. Seit Java 5 gibt es die Klasse CyclicBarrier im Paket java.util.concurrent, die eine solche Barriere realisiert. Der Vorteil gegenüber join() besteht in der Tatsache, dass der für die Abarbeitung verwendete Thread nicht enden muss – und ein Thread im Thread-Pool endet eigentlich nicht –, sondern dass er mit await() sein »Bin-fertig«-Signal geben kann. Das Buch-Beispiel com/tutego/insel/thread/concurrent/ArraySummer.java, das im Netz und auf der DVD zu finden ist, zeigt anhand eines parallelen Summierers die Funktionsweise.

Stop and go mit Exchanger

Die Klasse java.util.concurrent.Exchanger<V> dient ebenfalls dem Zusammenkommen von Threads, die jedoch bei ihrem Rendezvous Daten austauschen können. Ein üblicher Fall betrifft das Füllen von Puffern, etwa wenn ein Thread Daten vom Datensystem liest und ein anderer Thread die Daten über das Netzwerk weiterschickt. Ein Dateisystem-Thread füllt den Puffer, und wenn er komplett gefüllt ist, trifft er sich mit einem anderen Netzwerk-Thread, dem er den vollen Puffer gibt und von dem er wieder einen leeren Puffer empfängt. Der Netzwerk-Thread kann dann den Inhalt des Puffers wieder »verbrauchen« und der erste Thread den Puffer wieder mit Daten vom Dateisystem füllen.


Galileo Computing - Zum Seitenanfang

11.3.9 Arbeit niederlegen und wieder aufnehmen Zur nächsten ÜberschriftZur vorigen Überschrift

Wollen wir erreichen, dass ein Thread für eine bestimmte Zeit die Arbeit niederlegt und ein anderer den schlafenden Thread wieder aufwecken kann, müssten wir das selbst implementieren. Zwar gibt es mit suspend() und resume() zwei Methoden, doch sind sie veraltet, da sie ähnlich problematisch sind wie stop().


class java.lang.Thread 
implements Runnable

  • final void suspend()
    Haben wir die Möglichkeit, auf den Thread zuzugreifen (checkAccess() regelt dies wieder), und der Thread lebt, wird er so lange eingefroren (schlafen gelegt), bis resume() aufgerufen wird.
  • final void resume()
    Weckt einen durch suspend() lahmgelegten Thread wieder auf, der dann wieder arbeiten kann.

Galileo Computing - Zum Seitenanfang

11.3.10 Priorität Zur nächsten ÜberschriftZur vorigen Überschrift

Jeder Thread verfügt über eine Priorität, die aussagt, wie viel Rechenzeit ein Thread relativ zu anderen Threads erhält. Die Priorität ist eine Zahl zwischen Thread.MIN_PRIORITY (1) und Thread.MAX_PRIORITY (10). Durch den Wert kann der Scheduler erkennen, welchem Thread er den Vorzug geben soll, wenn mehrere Threads auf Rechenzeit warten. Bei seiner Initialisierung bekommt jeder Thread die Priorität des erzeugenden Threads. Normalerweise ist es die Priorität Thread.NORM_PRIORITY (5).

Das Betriebssystem (oder die JVM) nimmt die Threads immer entsprechend der Priorität aus der Warteschlange heraus (daher Prioritätswarteschlange). Ein Thread mit der Priorität N wird vor allen Threads mit der Wichtigkeit kleiner N, aber hinter denen der Priorität größer gleich N gesetzt. Ruft nun ein kooperativer Thread mit der Priorität N die Methode yield() auf, bekommt ein Thread mit der Priorität <= N auch eine Chance zur Ausführung.

Die Priorität kann durch Aufruf von setPriority() geändert und mit getPriority() abgefragt werden. Allerdings macht Java nur sehr schwache Aussagen über die Bedeutung und Auswirkung von Thread-Prioritäten.


Beispiel Wir weisen dem Thread t die höchste Priorität zu:

t.setPriority( Thread.MAX_PRIORITY );


class java.lang.Thread 
implements Runnable

  • final int getPriority()
    Liefert die Priorität des Threads.
  • final void setPriority( int newPriority )
    Setzt die Priorität des Threads. Es ergibt eine IllegalArgumentException, wenn die Priorität nicht zwischen MIN_PRIORITY (1) und MAX_PRIORITY (10) liegt.

Granularität und Vorrang

Die zehn Prioritätsstufen garantieren nicht zwingend unterschiedliche Ausführungen. Obwohl anzunehmen ist, dass ein Thread mit der Priorität NORM_PRIORITY+1 häufiger Programmcode ausführt als ein Thread mit der Priorität NORM_PRIORITY, kann ein Betriebssystem dies anders implementieren. Nehmen wir an, die Plattform implementiert lediglich fünf Prioritätsstufen. Ist 1 die niedrigste Stufe und 5 die höchste – die mittlere Stufe ist 3 –, werden wahrscheinlich NORM_PRIORITY und NORM_PRIORITY + 1 auf die Stufe 3 transformiert und haben demnach dieselbe Priorität. Was wir daraus lernen: Auch bei unterschiedlichen Prioritäten können wir nicht erwarten, dass ein bestimmtes Programmstück zwingend schneller läuft. Zudem gibt es Betriebssysteme mit Schedulern, die keine Prioritäten unterstützen oder diese unerwartet interpretieren.


Galileo Computing - Zum Seitenanfang

11.3.11 Der Thread als Dämon topZur vorigen Überschrift

Ein Server reagiert oft in einer Endlosschleife auf eingehende Aufträge vom Netzwerk und führt die gewünschte Aufgabe aus. In unseren bisherigen Programmen haben wir oft Endlosschleifen eingesetzt, sodass ein gestarteter Thread nie beendet wird. Wenn also run(), wie in den vorangehenden Beispielen, nie abbricht (Informatiker sprechen hier von terminiert), läuft der Thread immer weiter, auch wenn die Hauptapplikation beendet ist. Dies ist nicht immer beabsichtigt, da vielleicht Server-Funktionalität nach Beenden der Applikation nicht mehr gefragt ist. Dann sollte auch der endlos laufende Thread beendet werden. Um dies auszudrücken, erhält ein im Hintergrund arbeitender Thread eine spezielle Kennung: Der Thread wird als Dämon [Das griechische Wort (engl. daemon) bezeichnet allerlei Wesen zwischen Gott und Teufel.] gekennzeichnet. Standardmäßig ist ein aufgebauter Thread kein Dämon.

Ein Dämon ist wie ein Heinzelmännchen im Hintergrund mit einer Aufgabe beschäftigt. Wenn das Hauptprogramm beendet ist und die Laufzeitumgebung erkennt, dass kein normaler Thread läuft, sondern nur Dämonen, dann ist das Ende für die Dämonen eingeläutet. Das ist wie bei den Göttern der Scheibenwelt: Glaubt keiner an sie, hören sie auf zu existieren, obwohl der Dämon-Thread noch nicht terminiert hat. Wir müssen uns also um das Ende des Dämons nicht kümmern. Das ist etwa bei Überwachungsaufgaben ganz praktisch. Wenn es nichts mehr zu überwachen gibt, kann sich auch der Dämon in Luft auflösen.

Einen Thread in Java als Dämon zu kennzeichnen, heißt, die Methode setDaemon() mit dem Argument true aufzurufen. Die Methode ist nur vor dem Starten des Threads erlaubt. Danach kann der Status nicht wieder vom Dämon in den normalen Benutzer-Thread umgesetzt werden. Die Auswirkungen von setDaemon(true) können wir am folgenden Programm ablesen:

Listing 11.9 com/tutego/insel/thread/DaemonThread.java

package com.tutego.insel.thread; 
 
class DaemonThread extends Thread 
{ 
  DaemonThread() 
  { 
    setDaemon( true ); 
  } 
 
  @Override 
  public void run() 
  { 
    while ( true ) 
      System.out.println( "Lauf, Thread, lauf" ); 
  } 
 
  public static void main( String[] args ) 
  { 
    new DaemonThread().start(); 
  } 
}

In diesem Programm wird der Thread gestartet, und danach ist die Anwendung sofort beendet. Vor dem Ende kann der neue Thread aber schon einige Zeilen auf der Konsole ausgeben. Klammern wir die Anweisung mit setDaemon(true) aus, läuft das Programm ewig, da die Laufzeitumgebung auf das natürliche Ende der Thread-Aktivität wartet.


class java.lang.Thread 
implements Runnable

  • final void setDaemon( boolean on )
    Markiert den Thread als Dämon oder normalen Thread.
  • final boolean isDaemon()
    Testet, ob der Thread ein Dämon-Thread ist.

AWT und Dämonen

Obwohl Dämonen für Hintergrundaufgaben eine gute Einrichtung sind, ist der AWT-Thread kein Dämon. Unterschiedliche AWT-Threads sind normale Benutzer-Threads, dazu gehören AWT-Input, AWT-Motif oder Screen_Updater. Dies bedeutet, dass bei einmaliger Nutzung einer AWT-Funktion ein spezieller Nicht-Dämon-Thread gestartet wird, sodass die Applikation nicht automatisch beendet wird, wenn das Hauptprogramm endet. Daher muss in vielen Fällen die Applikation hart mit System.exit() beendet werden.



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 ist auch eine Insel





Java ist auch eine Insel
Jetzt bestellen


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

 Tipp
Zum Katalog: Coding for Fun





 Coding for Fun


 Buchempfehlungen
Zum Katalog: Objektorientierte Programmierung





 Objektorientierte
 Programmierung


Zum Katalog: Einstieg in Eclipse 3.4






 Einstieg in
 Eclipse 3.4


Zum Katalog: Java 6 lernen mit Eclipse






 Java 6 lernen
 mit Eclipse


Zum Katalog: NetBeans Platform 6






 NetBeans
 Platform 6


Zum Katalog: Java und XML






 Java und XML


Zum Katalog: Visual C# 2008






 Visual C# 2008


Zum Katalog: IT-Handbuch für Fachinformatiker






 IT-Handbuch für
 Fachinformatiker


Zum Katalog: C++ von A bis Z






 C++ von A bis Z


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




Copyright © Galileo Press 2009
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> 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