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

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 9 Threads und nebenläufige Programmierung
  gp 9.1 Prozesse und Threads
    gp 9.1.1 Wie parallele Programme die Geschwindigkeit steigern können
  gp 9.2 Threads erzeugen
    gp 9.2.1 Threads über die Schnittstelle Runnable implementieren
    gp 9.2.2 Thread mit Runnable starten
    gp 9.2.3 Der Name eines Threads
    gp 9.2.4 Die Klasse Thread erweitern
    gp 9.2.5 Wer bin ich?
  gp 9.3 Der Ausführer (Executor) kommt
    gp 9.3.1 Die Schnittstelle Executor
    gp 9.3.2 Die Thread-Pools
    gp 9.3.3 Threads mit Rückgabe über Callable
    gp 9.3.4 Mehrere Callable abarbeiten
    gp 9.3.5 Mit ScheduledExecutorService wiederholende Ausgaben und Zeitsteuerungen
  gp 9.4 Die Zustände eines Threads
    gp 9.4.1 Threads schlafen
    gp 9.4.2 Das Ende eines Threads
    gp 9.4.3 UncaughtExceptionHandler für unbehandelte Ausnahmen
    gp 9.4.4 Einen Thread höflich mit Interrupt beenden
    gp 9.4.5 Der stop() von außen und die Rettung mit ThreadDeath
    gp 9.4.6 Ein Rendezvous mit join() und Barrier sowie Austausch mit Exchanger
    gp 9.4.7 Mit yield() auf Rechenzeit verzichten
    gp 9.4.8 Arbeit niederlegen und wieder aufnehmen
    gp 9.4.9 Priorität
    gp 9.4.10 Der Thread ist ein Dämon
  gp 9.5 Synchronisation über kritische Abschnitte
    gp 9.5.1 Gemeinsam genutzte Daten
    gp 9.5.2 Probleme beim gemeinsamen Zugriff und kritische Abschnitte
    gp 9.5.3 Punkte parallel initialisieren
    gp 9.5.4 i++ sieht atomar aus, ist es aber nicht
    gp 9.5.5 Kritische Abschnitte schützen
    gp 9.5.6 Schützen mit ReentrantLock
    gp 9.5.7 Synchronisieren mit synchronized
    gp 9.5.8 Synchronized-Methoden der Klasse StringBuffer
    gp 9.5.9 Mit synchronized synchronisierte Blöcke
    gp 9.5.10 Look-Freigabe im Fall von Exceptions
    gp 9.5.11 Mit synchronized nachträglich synchronisieren
    gp 9.5.12 Monitore sind reentrant, gut für die Geschwindigkeit
    gp 9.5.13 Synchronisierte Methodenaufrufe zusammenfassen
    gp 9.5.14 Deadlocks
    gp 9.5.15 Erkennen von Deadlocks
  gp 9.6 Synchronisation über Warten und Benachrichtigen
    gp 9.6.1 Die Schnittstelle Condition
    gp 9.6.2 Beispiel Erzeuger-Verbraucher-Programm
    gp 9.6.3 Warten mit wait() und Aufwecken mit notify()
    gp 9.6.4 Falls der Lock fehlt: IllegalMonitorStateException
    gp 9.6.5 Semaphoren
  gp 9.7 Atomares und frische Werte mit volatile
    gp 9.7.1 Der Modifizierer volatile bei Objekt-/Klassenvariablen
    gp 9.7.2 Das Paket java.util.concurrent.atomic
  gp 9.8 Mit dem Thread verbundene Variablen
    gp 9.8.1 ThreadLocal
    gp 9.8.2 InheritableThreadLocal
  gp 9.9 Gruppen von Threads in einer Thread-Gruppe
    gp 9.9.1 Aktive Threads in der Umgebung
    gp 9.9.2 Etwas über die aktuelle Thread-Gruppe herausfinden
    gp 9.9.3 Threads in einer Thread-Gruppe anlegen
    gp 9.9.4 Methoden von Thread und ThreadGroup im Vergleich
  gp 9.10 Die Klassen Timer und TimerTask
    gp 9.10.1 Job-Scheduler Quartz
  gp 9.11 Einen Abbruch der virtuellen Maschine erkennen


Galileo Computing

9.2 Threads erzeugedowntop

Für nebenläufige Programme in Java ist die Klasse Thread zuständig, und jeder laufende Thread ist ein Exemplar dieser Klasse. Die folgenden Abschnitte machen deutlich, wie der nebenläufige Programmcode verpackt und dem Thread zur Ausführung vorgelegt wird.


Galileo Computing

9.2.1 Threads über die Schnittstelle Runnable implementieredowntop

Damit der Thread weiß, was er ausführen soll, müssen wir ihm Anweisungsfolgen geben. Diese werden in einem Befehls-Objekt vom Typ Runnable verpackt und dem Thread übergeben. Wird der Thread gestartet, arbeitet er die Programmzeilen aus dem Befehls-Objekt parallel ab. Die Schnittstelle Runnable ist schmal und schreibt nur eine run()-Methode vor.


interface java.lang.  Runnable  

gp  void run() Diese Methode enthält den parallel auszuführenden Programmcode.

Abbildung
Hier klicken, um das Bild zu Vergrößern

Wir wollen zwei Threads angeben, wobei einer zwanzigmal das aktuelle Datum und die Uhrzeit ausgibt und der andere einfach eine Zahl.

Listing 9.1   DateCommand.java

import java.util.Date;
public class DateCommand   implements Runnable
  {
    public void run()
    {
    for ( int i = 0; i < 20; i++ )
      System.out.println( new Date() );
  }
}

Listing 9.2   CounterCommand.java

class CounterCommand   implements Runnable
  {
    public void run()
    {
    for ( int i = 0; i < 20; i++ )
      System.out.println( i );
  }
}

Unser parallel auszuführender Programmcode in run() besteht aus einer Schleife, die in einem Fall ein aktuelles Date-Objekt ausgibt und im anderen Fall einen Schleifenzähler.


Galileo Computing

9.2.2 Thread mit Runnable starten  downtop

Nun reicht es nicht aus, einfach die run()-Methode einer Klasse direkt aufzurufen. Würden wir dies tun, dann wäre nichts nebenläufig, sondern wir würden einfach eine Methode sequenziell ausführen. Damit der Programmcode parallel zur Applikation läuft, müssen wir ein Thread-Objekt erzeugen und dann den Thread explizit starten. Dazu übergeben wir dem Konstruktor der Klasse Thread eine Referenz auf das Runnable-Objekt und rufen start() auf. Nachdem start() für den Thread eine Ablaufumgebung geschaffen hat, ruft es dann selbstständig die Methode run() genau einmal auf. Läuft der Thread schon, so wirft ein zweiter Aufruf der start()-Methode eine IllegalThreadStateException aus.


class java.lang.  Thread
  implements Runnable

gp  Thread( Runnable target ) Erzeugt einen neuen Thread mit einem Runnable, welches den parallel auszuführenden Programmcode vorgibt.
gp  void start() Ein neuer Thread – neben dem die Methode aufrufenden Thread – wird gestartet. Der neue Thread führt die run()-Methode nebenläufig aus. Jeder Thread kann nur einmal gestartet werden.

Listing 9.3   FirstThread.java

public class FirstThread
{
  public static void main( String[] args )
  {
    Thread t1 =   new Thread( new DateCommand() );
      t1.  start();
      Thread t2 =   new Thread( new CounterCommand() );
      t2.  start();
    }

}

Beim Starten des Programms erfolgt eine Ausgabe auf dem Bildschirm, die in etwa so aussehen kann:

Thu Jan 18 15:40:20 CET 2006
0
1
2
3
4
5
6
7
8
9
Thu Jan 18 15:40:20 CET 2006
10
...

Deutlich ist die Verzahnung der beiden Threads zu erkennen. Was allerdings auf den ersten Blick etwas merkwürdig wirkt, ist die erste Zeile des Datum-Threads und viele weitere Zeilen des Zähl-Threads. Dies hat jedoch nichts zu sagen und zeigt deutlich den Nichtdeterminismus bei Threads. Interpretiert werden kann dies jedoch durch die unterschiedlichen Laufzeiten, die für die Aufgaben »gib Datum aus« und »gib Zahl« nötig sind.

Automatisches Starten während der Erzeugung

Erst der Aufruf der start()-Methode lässt den Thread ablaufen. Etwas eleganter ist der Weg, dass das Objekt seinen eigenen Thread verwaltet, der im Konstruktor gestartet wird. Dann muss dort ein Thread-Objekt für die Runnable-Umgebung angelegt und die start()-Methode ausgeführt werden. An einem Beispiel ausprobiert, kann das so aussehen: Eine Klasse DateThreadAutoStart soll beim Anlegen automatisch einen Thread starten. Dazu initialisiert der Standard-Konstruktor von DateThreadAutoStart ein neues Thread-Objekt mit der this-Referenz als Argument.

Listing 9.4   DateThreadAutoStart.java

class DateThreadAutoStart implements Runnable
{
    DateThreadAutoStart()
    {
      new Thread( this ).start();
    }
  public void run()
  {
    for ( int i = 0; i < 20; i++ )
      System.out.println( new Date() );
  }
}

Hinweis   Wenn ein Thread im Konstruktor gebildet wird, dann sollte die Arbeitsweise bei Vererbung beachtet werden. Nehmen wir an, eine Klasse leitet von einer anderen Klasse ab, die im Konstruktor einen Thread startet. Bildet die Applikation ein Exemplar der Unterklasse, so werden in der Bildung des Objektes immer erst die Konstruktoren der Oberklasse aufgerufen. Dies hat zur Konsequenz, dass der Thread schon läuft, auch wenn das Objekt noch nicht ganz gebaut ist. Die Erzeugung ist erst abgeschlossen, wenn nach dem Aufruf der Konstruktoren der Oberklassen der eigene Konstruktor vollständig abgearbeitet wurde.


Galileo Computing

9.2.3 Der Name eines Threads  downtop

Ein Thread hat eine ganze Menge Eigenschaften, wie Zustand, Priorität und auch einen Namen. Er kann mit setName() gesetzt und mit getName() erfragt werden.


class java.lang.  Thread
  implements Runnable

gp  Thread( Runnable target, String name ) Erzeugt ein neues Thread-Objekt mit einem Runnable und setzt den Namen.
gp  final String getName() Liefert den Namen des Threads. Der Name wird im Konstruktor angegeben oder mit setName() zugewiesen. Standardmäßig ist der Name »Thread-x«, wobei x eine eindeutige Nummer ist.
gp  final void setName( String name ) Ändert den Namen des Threads. Wenn wir den Namen nicht ändern können, wird eine SecurityException ausgelöst.

Galileo Computing

9.2.4 Die Klasse Thread erweitern  downtop

Da die Klasse Thread selbst die Schnittstelle Runnable implementiert und die run()-Methode mit leerem Programmcode bereitstellt, können wir auch Thread erweitern, wenn wir eigene parallele Aktivitäten programmieren wollen. Dann müssen wir kein Runnable-Exemplar mehr in den Konstruktor einfügen, denn wenn unsere Klasse Unterklasse von Thread ist, reicht ein Aufruf der geerbten Methode start(). Danach arbeitet das Programm direkt weiter, führt also kurze Zeit später die nächste Anweisung hinter start() aus. start() kann von uns auch überschrieben werden, was aber nur selten sinnvoll beziehungsweise nötig ist. Wir müssen dann darauf achten, super.start() aufzurufen, damit der Thread wirklich losrennt.


class java.lang.  Thread
  implements Runnable

gp  void run() Diese Methode enthält den parallel auszuführenden Programmcode.
gp  Thread( String name ) Erzeugt ein neues Thread-Objekt und setzt den Namen. Sinnvoll bei Unterklassen, die den Konstruktor über super(name) aufrufen.

Beispiel   Ein paralleles Programm, das das Datum ausgibt.
public class DateThread   extends Thread
  {
  @Override
    public void run()
    {
    for ( int i = 0; i < 20; i++ )
      System.out.println( new Date() );
  }
}

Abbildung
Hier klicken, um das Bild zu Vergrößern

Der Startschuss fällt für den Thread wieder beim Aufruf von start().

Thread t = new DateThread();
  t.start();  

Oder auch ohne Zwischenspeicherung der Objektreferenz:

new DateThread().start();

Damit wir als Thread-Benutzer nicht erst die start()-Methode aufrufen müssen, kann ein Thread sich auch wieder selbst starten. Der Konstruktor enthält den Methodenaufruf start().

class DateThread extends Thread
{
  DateThread()
  {
      start();
    }
  ...  der Rest bleibt ...
}

Was passiert, wenn wir run() an Stelle von start() aufrufen?

Ein Programmierfehler, der hin und wieder passiert, ist folgender: Statt start() rufen wir aus Versehen run() auf. Was geschieht? Fast genau das Gleiche wie bei start(), nur mit dem Unterschied, dass die Objektmethode run() nicht parallel zum übrigen Programm abgearbeitet wird. Der aktuelle Thread bearbeitet die run()-Methode sequenziell, bis sie zu Ende ist und die Anweisungen nach dem Aufruf an die Reihe kommen. Der Fehler fällt nicht immer direkt auf, denn die Aktionen in run() finden ja statt – nur nicht nebenläufig.

Erweitern von Thread oder Implementieren von Runnable?

Die beste Idee ist, Runnable-Objekte zu bauen, die dann dem Thread übergeben werden. Befehlsobjekte dieser Art sind recht flexibel, da die einfachen Runnable-Objekte leicht übergeben und sogar von Thread aus einem Thread-Pool ausgeführt werden können. Ein Nachteil der Thread-Erweiterung ist, dass die Einfachvererbung störend sein kann; erbt eine Klasse von Thread, ist die Erweiterung schon »aufgebraucht«. Doch ob eine Klasse Runnable implementiert oder Thread erweitert, eins bleibt: eine neue Klasse.


Galileo Computing

9.2.5 Wer bin ich?  toptop

Eine Erweiterung der Klasse Thread hat den Vorteil, dass geerbte Methoden wie getName() sofort genutzt werden können. Wenn wir Runnable implementieren, dann genießen wir den Vorteil nicht.

Die Klasse Thread liefert mit der Klassenmethode currentThread() die Objektreferenz für das Thread-Exemplar, das diese Anweisung Methode ausführt. Auf diese Weise können auf dem eigenen Thread die Thread-Methoden verwendet werden?


class java.lang.  Thread
  implements Runnable

gp  static Thread currentThread() Liefert den Thread, der dieses Programmstück ausführt.

Beispiel   Die aktuelle Priorität des laufenden Threads soll ausgegeben werden.
System.out.println( Thread.currentThread().getPriority() );

Falls es in einer Schleife wiederholten Zugriff auf Thread.currentThread() gibt, sollte das Ergebnis zwischengespeichert werden, denn der Aufruf der nativen Methoden ist nicht ganz billig.

 << zurück




Copyright © Galileo Press GmbH 2005
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 GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de