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.3 Der Ausführer (Executor) kommdowntop

Damit der Thread ein Runnable ausführt, muss immer ein neues Thread-Objekt aufgebaut werden, denn start() ist nur einmal auf einem Thread-Objekt erlaubt. Würde der Thread während seines Lebenszyklus eine Reihe unterschiedlicher Runnable abarbeiten wollen, ist eine andere Umsetzung nötig. Das Gleiche gilt, wenn das Runnable nicht sofort, sondern später (Ostern) oder wiederholt (immer Weihnachten) ausgeführt werden soll.


Galileo Computing

9.3.1 Die Schnittstelle Executor  downtop

Seit Java 5 gibt es eine Abstraktion für Klassen, die Befehle über Runnable ausführen. Die Schnittstelle Executor schreibt eine Methode vor:


interface java.util.concurrent.  Executor  

gp  void execute( Runnable command ) Wird später von Klassen implemenitert, die ein Runnable abarbeiten können.

Jeder, der nun Befehle über Runnable abarbeitet, ist Executor. Von dieser Schnittstelle gibt es bisher zwei wichtige Implementierungen:

gp  ThreadPoolExecutor. Die Klasse baut eine Sammlung von Threads auf, den Thread-Pool. Ausführungsanfragen werden von den freien Threads übernommen.
gp  ScheduledThreadPoolExecutor. Eine Erweiterung von ThreadPoolExecutor um die Fähigkeit, zu bestimmen Zeiten oder mit bestimmten Wiederholungen Befehle abzuarbeiten.

Die beiden Klassen haben nicht ganz so triviale Konstruktoren, und eine Utility-Klasse vereinfacht den Aufbau dieser speziellen Executor-Objekte.


class java.util.concurrent.  Executors  

gp  static ExecutorService newCachedThreadPool() Einen Thread-Pool mit wachsender Größe.
gp  static ExecutorService newFixedThreadPool( int nThreads ) Einen Thread-Pool mit maximal nThreads.
gp  static ScheduledExecutorService newSingleThreadScheduledExecutor() static ScheduledExecutorService newScheduledThreadPool( int corePoolSize ) Spezielle Executor-Objekte, um Wiederholungen festzulegen.

ExecutorService ist eine Schnittstelle, die Executor erweitert. Unter anderem sind hier Funktionen zu finden, die die Ausführer herunterfahren. Im Falle von Thread-Pools ist das nützlich, da die Threads ja sonst nicht beendet würden, weil sie auf neue Aufgaben warten.


Galileo Computing

9.3.2 Die Thread-Pools  downtop

Die wichtige Funktion der Klasse Executors ist newCachedThreadPool(). Das Ergebnis ist ein ExecutorService, unter anderem mit folgenden Funktionen.


interface java.util.concurrent.  ExecutorService
  extends Executor

gp  void shutdown() Fährt den Thread-Pool runter. Laufende Threads werden nicht abgebrochen, aber neue Anfragen nicht angenommen.
gp  boolean isShutdown() Wurde der Executor schon runtergefahren?
gp  List<Runnable> shutdownNow() Gerade ausführende Befehle werden zum Stoppen angeregt. Die Rückgabe ist eine Liste der zu beendenden Kommandos.

Der Thread-Pool nimmt über excecute() – dazu reicht die Schnittstelle Executor – die Runnable-Objekte an, was das folgende Beispiel mit Runnable r1 und r2 zeigt:

Listing 9.5   ThreadPoolDemo.java, main()-Funktion

Runnable r1 = new Runnable() {
  public void run() {
    System.out.println( "A1 " + Thread.currentThread() );
    System.out.println( "A2 " + Thread.currentThread() );
  }
};
Runnable r2 = new Runnable() {
  public void run() {
    System.out.println( "B1 " + Thread.currentThread() );
    System.out.println( "B2 " + Thread.currentThread() );
  }
};

Jetzt lässt sich der Thread-Pool als ExecutorService beziehen und die Befehle ausführen:

ExecutorService executor = Executors.newCachedThreadPool();
executor.execute( r1 );
executor.execute( r2 );
Thread.sleep( 500 );
executor.execute( r1 );
executor.execute( r2 );
executor.shutdown();

Die Ausgabe zeigt sehr schön die Wiederverwendung der Threads.

A1 Thread[pool-1-thread-1,5,main]
A2 Thread[pool-1-thread-1,5,main]
B1 Thread[pool-1-thread-2,5,main]
B2 Thread[pool-1-thread-2,5,main]
B1 Thread[pool-1-thread-1,5,main]
B2 Thread[pool-1-thread-1,5,main]
A1 Thread[pool-1-thread-2,5,main]
A2 Thread[pool-1-thread-2,5,main]

Die toString()-Methode von Thread ist so implementiert, dass zunächst der Name der Threads auftaucht, den die Pool-Implementierung so gesetzt hat, dann die Priorität und der Name des Threads, der den neuen Thread gestartet hat. Am neuen Namen ist abzulesen, dass hier zwei Threads von einem Thread-Pool 1 verwendet werden: thread-1 und thread-2. Nach dem Ausführen der beiden Aufträge und der kleinen Warterei sind die Threads fertig und für neue Jobs bereit, sodass A1 und A2 beim zweiten Mal mit den wieder freien Threads abgearbeitet werden.


Galileo Computing

9.3.3 Threads mit Rückgabe über Callable  downtop

Der nebenläufige Thread kann nur über Umwege dem aufrufenden Programm Ergebnisse mitteilen. In einer eigenen Klasse, die Runnable erweitert, lässt sich im Konstruktor zum Beispiel eine Datenstruktur übergeben, in die das Thread das Ergebnis hineinlegt. Die Datenstruktur kann dann vom Aufrufer auf Änderungen abgeführt werden.

Seit Java 5 gibt es neben Runnable die Schnittstelle Callable, die dem Aufrufer eine Rückgabe übermittelt.


interface java.util.concurrent.  Callable<V>  

gp  V call() Diese Methode enthält den parallel auszuführenden Programmcode und liefert eine Rückgabe vom Typ V.

Wir wollen nun ein Beispiel angeben, das ein Feld sortiert. Das Sortieren soll ein Callable im Hintergrund übernehmen. Ist die Operation beendet, soll der Verweis auf das Feld zurückgegeben werden. Das Sortieren erledigen wie üblich Arrays.sort().

Listing 9.6   CallableDemo.java, MyCallable

class MyCallable implements Callable<byte[]>
{
  private final byte[] b;
  MyCallable( byte[] b )
  {
    this.b = b;
  }
  public byte[] call()
  {
    Arrays.sort( b );
    return b;
  }
}

Natürlich bringt es wenig, das Callable-Objekt aufzubauen und selbst call() aufrufen, denn ein Thread soll die Aufgabe im Hintergrund erledigen. Dazu ist jedoch nicht die Klasse Thread selbst zu verwenden, sondern ein ExcutorService, den wir etwa über Executors.newCachedThreadPool() bekommen.

Listing 9.7   CallableDemo.java, main()

byte[] b = new byte[ 4000000 ];
new Random().nextBytes( b );
MyCallable c = new MyCallable( b );
ExecutorService executor = Executors.newCachedThreadPool();

Der ExcutorService bietet dann eine submit() Methode, die das Callable annimmt und einen Thread für die Abarbeitung aussucht. Weil das Ergebnis asynchron ankommt, liefert submit() ein Future-Objekt zurück, über das wir herausfinden können, ob das Ergebnis schon da ist oder wir noch warten müssen.

Future<byte[]> result = executor.submit( c );

Nach dieser Anweisung ist die beste Zeit, noch andere parallele Aufgaben anzustoßen, um später die Ergebnisse einzusammeln.

Unsere Variable result verrät mit isDone(), ob die Aufgabe schon erledigt ist. Rufen wir get() auf, blockiert die Methode so lange, bis das Ergebnis da ist.

byte[] bs = result.get();
System.out.printf( "%d, %d, %d%n"bs[0]bs[1]bs[bs.length-1] ); // –128, –128, 127

Nicht immer ist das Blockieren erwünscht. Für diesen Fall ermöglicht die überladene Funktion von get() eine Parametrisierung mit einer Wartezeit und Zeiteinheit.

byte[] bs = result.get( 2TimeUnit.SECONDS );

Ist das Ergebnis nicht innerhalb von 2 Sekunden verfügbar, löst die Methode eine Exception aus, die, auf dem Bildschirm ausgegeben, so aussehen wird:

java.util.concurrent.TimeoutException
  at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:211)
  at java.util.concurrent.FutureTask.get(FutureTask.java:85)
  at CallableDemo.main(CallableDemo.java:26)

Ein Runnable mit Zukunft oder als Callable

Aus Gründen der Symmetrie gibt es neben submit(Callable) noch zwei submit()-Methoden, die ebenfalls ein Runnable annehmen. Zusammen ergeben sich:


interface java.util.concurrent.  ExecutorService
  extends Executor

gp  <T> Future<T> submit( Callable<T> task ) Der ExecutorService soll die Aufgabe abarbeiten und Zugriff auf das Ergebnis über die Rückgabe geben.
gp  Future<?> submit( Runnable task ) Der ExecutorService arbeitet das Runnable ab und ermöglicht es, über das Future-Objekt zu erfragen, ob die Augabe schon abgearbeitet wurde oder nicht. get() liefert am Ende null.
gp  <T> Future<T> submit( Runnable task, T result ) Wie submit(task), nur: die get()-Anfrage über Future liefert result.

Zum Umbau eine Runnable in ein Callable gibt es noch einige Hilfsfunktionen in Executors. Dazu zählt die statische Methode callable(Runnable task), die in Callable<Object> liefert und callable(Runnable task, T result), das ein Callable<T> zurückgibt.


Galileo Computing

9.3.4 Mehrere Callable abarbeiten  downtop

Die Methode submit()vom ExecutorService nimmt genau ein Callable an und führt es aus. Doch die Klasse kann auch mehrere Callable zu gleicher Zeit ausführen:


interface java.util.concurrent.  ExecutorService
  extends Executor

gp  <T> List<Future<T>> invokeAll( Collection<Callable<T>> tasks ) Beginnt mit der Ausführung aller Aufgaben und liefert eine Liste von Future-Objekten, die die Ergebnisse repräsentieren.
gp  <T> List<Future<T>> invokeAll( Collection<Callable<T>> tasks, long timeout,                                   TimeUnit unit ) Führt alle Ausgaben aus und würde die Ergenisse als Liste von Future-Objekten liefern, wenn die Zeit timeout in der gegebenen Zeiteinheit nicht überschritten wird.
gp  <T> T invokeAny( Collection<Callable<T>> tasks ) Führt alle Aufgaben aus, aber liefert das Ergebnis des Threads, der als Erster fertig ist. Ein get() wird also nie warten müssen.
gp  <T> T invokeAny(Collection<Callable<T>> tasks, long timeout, TimeUnit unit) Führt alle Aufgaben aus, gibt aber nur eine beschränkte Zeit. Der erste, der in der Zeit fertig wird, wird zurückgegeben.

Galileo Computing

9.3.5 Mit ScheduledExecutorService wiederholende Ausgaben und Zeitsteuerungen  toptop

Die Klasse ScheduledThreadPoolExecutor ist eine weitere Klasse neben ThreadPoolExecutor, die die Schnittstelle Excecutor und ExecutorService implementiert. Die wichtige Schnittstelle, die diese Klasse außerdem implementiert, ist aber ScheduledExecutorService – sie schreibt scheduleXXX() Operationen vor, um ein Runnable oder Callable zu bestimmten Zeiten und Wiederholungen auszuführen. (Zwar gibt es mit dem java.util.Timer etwas Ähnliches, doch der ScheduledThreadPoolExecutor nutzt Threads aus dem Pool.)

Das folgende Beispiel führt nach einer Startzeit von einer Sekunde alle zwei Sekunden eine Ausgabe aus.

Listing 9.8   ScheduledExecutorServiceDemo.java

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo
{
  public static void main( String[] args )
  {
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool( 1 );
    ScheduledFuture<?> fut = scheduler.scheduleAtFixedRate(
        new Runnable() {
          public void run() {
            System.out.println( "Tata" );
          }
        },
        1 /* Startverzögerung */,
        2 /* Dauer */,
        TimeUnit.SECONDS );
  }
}
 << 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