|
|||||||||||||||||||
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | | | | |||||||||||||||||||
|
Java Multithread Support - Asynchron ausführbare Tätigkeiten (Callable & Future)
|
||||||||||||||||||
Wir haben uns bereits in der letzten Ausgabe dieser Kolumne (siehe
/
KRE7
/) einen Überblick über Neuerungen im
JDK 5.0 angesehen, die für die Multithread-Programmierung zur Verfügung
stehen. Darunter sind auch Abstraktionen, die das Ausführen
von Tätigkeiten in Threads unterstützen. Am spannendsten
ist dabei sicher der Threadpool, mit dessen Hilfe sich Tätigkeiten
auf bereitstehende Threads verteilen und sogar ggf. in einer Taskqueue
speichern lassen. Der Threadpool verwendet dabei aber nicht nur das
allseits bekannte Runnable zur Beschreibung einer asynchron ausführbaren
Tätigkeit, sondern arbeitet auch mit den in Java 5.0 neuen Abstraktionen
Callable und Future. In diesem Beitrag sehen wir uns, zur Vorbereitung
auf den nächsten Beitrag über Threadpools, die Interfaces Callable
und Future an.
Ausführen von ThreadsAbbildung 1 zeigt die Interfaces und Klassen, die für das Thema „Ausführen von Threads“ relevant sind. Dazu gehören nicht nur die Threadpools, sondern auch Callable, Future und anverwandte Abstraktionen.
Entsprechend der UML sind die Namen von Interfaces kursiv. Genauso wird bei der Vererbung zwischen extends-Beziehungen (Pfeil mit durchgezogener Linie) und implements-Beziehungen (Pfeil mit gestrichelter Linie) unterschieden. Das Diagramm ist relativ dicht gedrängt. Damit wird deutlich, wie umfangreich das Thema nun geworden ist. Bisher gab es an dieser Stelle ja nur das Interface Runnable. Alle anderen Abstraktionen sind mit dem JDK 5.0 neu dazugekommen. Eine weitere Neuerung des JDK 5.0, nämlich die Generics, werden bereits bei der Definition der neuen Abstraktionen genutzt. Future<V> ist zum Beispiel keine einfaches Interface, sondern ein generisches Interface mit einem Typparameter V.
Beginnen wir die Diskussion der neuen Abstraktionen mit den beiden neuen
zentralen Interfaces Callable<V> und Future<V>.
Callable<V>Vielleicht haben Sie sich auch schon einmal eine Alternative zu dem Runnable Interface gewünscht, die es erlaubt, Ergebnisse bzw. Exceptions zurückzuliefern. Mit dem JDK 5.0 gibt es nun eine solche Alternative: das generische Interface Callable<V>.Callable<V> ist ein Interface, das parallel zum bereits seit JDK 1.0 existierenden Runnable eingeführt wurde. Beide Interfaces erlauben es, eine definierte Funktionalität asynchron ausführen zu lassen. Wie wohl allgemein bekannt ist, implementiert man dazu beim Runnable die Methode: public void run(); Die in run() implementierte Funktionalität kann dann asynchron in einem Thread ausgeführt werden. Beim einem Callable<V> ist es ganz ähnlich. Es wird die folgende Methode implementiert: public V call() throws Exception; Dabei ist der Returntype V der Methode call() der Typparameter des generischen Interfaces Callable<V>. Mit diesem Typparameter ist es möglich, von einer asynchronen Funktionalität, die in call() implementiert wurde, ein Ergebnis eines beliebigen, aber zur Compilezeit festen, Referenztyps zurückzubekommen. Falls auf Grund eines Fehlers kein Ergebnis ermittelt werden kann, ist es möglich, eine Exception zu werfen. Das heißt, Callable<V> erlaubt es nun explizit, ein Ergebnis bzw. einen Fehler von einer asynchron ausgeführten Tätigkeit zu bekommen. Mit Runnable war das so nicht direkt möglich. Hier ist ein Beispiel für die Implementierung eines Callable, das die Anzahl der Millisekunden zurückliefert, die benötigt werden, um tausendmal "Hello World!" auszudrucken.
public class MyCallable implements Callable<Long> {
for (int I=0; I<1000; i++)
return ( System.currentTimeMillis()
– t);
Ein Problem ist jetzt natürlich: wie kann man ein Callable als eigenen Thread starten? Die Schnittstelle der Klasse Thread hat sich mit dem JDK 5.0 nicht so geändert, dass jetzt alternativ ein Thread mit einem Callable gestartet werden kann. Hier wird weiterhin ein Runnable erwartet. Es wäre genau betrachtet auch gar nicht sinnvoll gewesen, einem Thread ein Callable statt einem Runnable zu geben. Man hätte dann noch einen Mechanismus benötigt, um Ergebnisse oder Fehler des asynchron in einem eigenen Thread ablaufenden Callables zu ermitteln. Nachfolgend wollen wir uns den Adapter ansehen, der aus einem Callable<V> ein Runnable macht, das man einem Thread übergeben und starten kann. Aber vorher sehen wir uns erst einmal das Future-Pattern an. Die Adapterklasse FutureTask ist nämlich nicht nur ein Adapter, sondern bietet auch Unterstützung beim Abholen des Resultats eines Callable<V> - und darum geht es genau beim Future-Pattern. Abbildung 2 zeigt die alle Klassen und Interfaces, die im Zusammenhang mit Runnable und Callable relevant sind.
Future<V>Häufig will man erfahren, dass eine asynchrone Tätigkeit, die parallel zur eigentlichen Anwendung ausgeführt wird, fertig geworden ist, um ihr Ergebnis auszuwerten. Stellen Sie sich dazu zum Beispiel eine mit Swing implementierte GUI-Anwendung vor, die unter anderem die Funktionalität hat, Dateien von entfernten Rechnern herunterzuladen. Das Herunterladen geschieht dabei als asynchrone Tätigkeit in einem eigenen Thread parallel zur Anwendung. So ist die Anwendung auch während des Herunterladens in der Lage, Benutzerinput zu verarbeiten. Nach dem Herunterladen wird das erfolgreiche Beenden der Aktion zusammen mit dem Dateinamen, der Größe der Datei und der durchschnittlichen Geschwindigkeit angezeigt. Kommt es zu einem Fehler beim Herunterladen, so wird dieser stattdessen zusammen mit dem Dateinamen angezeigt. Das typische Problem ist nun: wie meldet sich der Thread, der das Herunterladen durchführt hat, am Ende zurück und teilt sein Ergebnis mit? Dafür gibt es zwei grundsätzlich unterschiedliche Lösungsansätze:
Wie funktioniert nun das Future-Pattern, dem eigentlich unsere Hauptaufmerksamkeit gilt? Hier hat man eine explizite Abstraktion, das Future, welche die Synchronisation zwischen den Threads sowie die Ergebnisübergabe übernimmt. Im generischen Interface Future<V> des JDK 5.0 stehen dafür folgende Methoden zur Verfügung: V get() throws InterruptedException, ExecutionException
Zum Interface Future<V> gibt eine Klasse in der Multithread-Support-Erweiterung des JDK 5.0, nämlich die FutureTask<V>, die die Funktionalität des Future Interfaces implementiert. FutureTask<V>FutureTask<V> ist eine wichtige Klasse der Multithread-Erweiterungen des JDK 5.0. Sie implementiert die beiden zentralen Interfaces Runnable und Future<V>. Vorstellen kann man sich die FutureTask als einen intelligente Wrapper für ein Runnable oder ein Callable. Einerseits implementiert die FutureTask die run() Funktionalität für die Ausführung einer asynchronen Tätigkeit, basierend auf dem unterliegenden Runnable oder Callable. Zusätzlich implementiert die FutureTask noch all die Methoden des Future, indem sie Ergebnisse und Exceptions von der asynchronen Tätigkeit aufsammelt, ggf. aufbewahrt und auf Abruf abliefert oder darauf wartet, falls nötig. Zusätzlich werden noch die Zustände der Tätigkeit („fertig“ / „abgebrochen“ / „noch nicht angefangen“) verwaltet und der Abbruch der Tätigkeit ermöglicht.Konstruieren läßt sich eine FutureTask<V> entweder mit einem Callable<V>, dessen Ergebnis sie beim Future<V>.get() zurückliefert, oder mit einem Runnable und einem vordefinierten Ergebniswert beliebigen Referenztyps, welcher beim Future<T>.get() zurückliefert wird.
FutureTask(Callable<V> callable)
Bekanntlich erzeugt ein Runnable gar kein Ergebnis, die FutureTask hat aber eine get() Methode, die ein Ergebnis zurückliefert. Damit die FutureTask später beim Abholen des „Ergebnisses“ etwas abliefern kann, muß bei der Konstruktion dieses „Ergebnis“ bereits mitgegeben werden. Dieser Wert, den man bei der Konstruktion der FutureTask zu dem Runnable dazugibt, ist sozusagen ein Dummy-Ergebnis. Wenn das durchgeschleuste Objekt gar nicht gebraucht wird, was häufig vorkommt, dann übergibt man üblicherweise Boolean.TRUE als Dummy. Mit der Klasse FutureTask<V> lassen sich also Runnables und Callable<V>s in Runnables verpacken und als asynchrone Tätigkeiten starten, indem sie dem Konstruktor von Thread übergeben werden und der Thread danach gestartet wird. Man kann auf die Beendigung der Tätigkeit, mit oder ohne Ergebnis, warten. Das macht man über das Interface Future<V>. Die asynchronen Tätigkeiten lassen sich vorzeitig beenden, ebenfalls über das Interface Future<V>. Insgesamt gesehen ist die FutureTask die zentrale Abstraktion, die das Ausführen (oder Abbrechen) einer asynchronen Tätigkeiten und das Entgegennehmen des resultierenden Ergebnisses (oder Fehlers) unterstützt. Auch unter einem anderen Gesichtspunkt ist FutureTask<V> eine wichtige Klasse. Sie ist der Adapter, der aus einem Callable<V> ein Runnable macht. Das neu eingeführt Interface Callable<V> hat zwar gegenüber dem Runnable den Vorteil, dass jetzt auch ein Ergebnis bzw. eine Exception von einer asynchronen Tätigkeit zurückgeliefert werden kann, aber fast überall wird weiterhin ein Runnable und kein Callable<V> als Parameter erwartet. Zum Beispiel gibt es keinen neuen Konstruktor für Thread, der es erlauben würde, einen Thread mit einem Callable<V> zu konstruieren. In solchen Fällen benutzt man die FutureTask<V> als Adapter. Schauen wir uns dazu ein Beispiel an. Das folgende Callable<Integer> ist ein primitiver Benchmark, der zählt, wie häufig „Hello World!“ innerhalb von 1 sec ausdruckt werden kann, und liefert diese Anzahl als Ergebnis zurück.
public class HelloWorldCount implements Callable<Integer> {
int i;
static public void main(String[] argv) {
new Thread(t).start(); // 2
try {
In der main-Methode wird nun unser HelloWorldCount (welches ein Callable<Integer> ist) mit Hilfe einer FutureTask<Integer> in ein Runnable adaptiert (Zeile 1) und in einem eigenen Thread als asynchrone Tätigkeit gestartet (Zeile 2). Der Rest der Methode besteht nur noch aus der Auswertung des Ergebnisses bzw. der Fehlers.
Das Beispiel zeigt u.a., wie man Exceptions, die von einer asynchronen
Tätigkeit ausgelöst werden, lokal über das Future fangen
und behandeln kann. Das ist einer der Vorzüge der Callables.
Wenn man mit Runnables arbeitet, dann können diese Runnables ohnehin
keine Checked Exceptions werfen, aber sie könnten noch immer Runtime
Exceptions (oder Errors) auslösen. Wenn ein Runnable so etwas
tut, dann kann es passieren, dass der Thread mit einer Runtime Exception
abbricht, ohne dass diese Ausnahmesituation irgendwo behandelt würde.
Was macht man dann?
Behandlung von ungefangenen Exceptions im Zusammenhang mit ThreadsRuntime Exceptions (oder Errors), die nicht in einem Thread abgefangen werden, führen dazu, dass der Thread beendet wird. Um auf ein solches Vorkommnis zu reagieren, kann man von der Klasse ThreadGroup ableiten und in ihrer uncaughtException() Methode eine geeignete Behandlung implementieren.Zwar ist die ThreadGroup seit dem JDK 1.0 in Java vorhanden, aber eigentlich spielt sie in der Multihread-Programmierung keine sehr wichtige Rolle. Deshalb gibt es mit dem JDK 5.0 ein neues in die Klasse Thread geschachteltes Interface: Thread.UncaughtExceptionHandler. Jede Klasse, die dieses Interface implementiert, kann nun als Handler für ungefangene Exceptions genutzt werden, die zur Beendigung eines Threads geführt haben. Dazu bietet die Klasse Thread zwei neue Methoden an:
class SomeRunnable implements Runnable {
Eine solche Lösung über einen Uncaught-Exception-Handler ist recht grobgranular. Sie gilt entweder für einen Thread, wie im Beispiel gezeigt, oder für eine Gruppe von Threads oder alle Threads. Man muß für das Einhängen eines Uncaught-Exception-Handlerist immer Zugriff auf den oder die Threads haben, die eine Tätigkeit ausführen. Wenn ein Thread mehrere Tätigkeiten ausführt, wie das zum Beispiel bei einem Threadpool der Fall ist, dann möchte man u.U. pro Tätigkeit eine gesonderte Fehlerbehandlung durchführen. Das läßt sich mit einem Uncaught-Exception-Handler nicht erreichen. Mit einem Future hingegen ist es problemlos möglich, die Runtime Exception (oder den Error) von einem Runnable ganz lokal abzuholen und zu behandeln. Dazu muß man das Runnable in eine FutureTask verpacken; dann kann über Future.get() die Runtime Exception (oder der Error) genau dieses einen Runnables gefangen werden. Das nachfolgende Beispiel skizziert eine solche Lösung:
class SomeRunnable implements Runnable {
Die Lösung über das Future ist im allgemeinen die elegantere
Lösung, weil sie lokal funktioniert, statt die Verantwortlichkeiten
über verschiedene Teile des Programms zu verteilen. Bei der
Lösung über einen Uncaught-Exception-Handler muß man sich
in die Erzeugung bzw. Konfiguration des ausführenden Threads einhängen,
um etwaige Runtime Exceptions (oder Errors) zu behandeln. Das ist nicht
immer möglich. Im nächsten Artikel werden wir sehen, daß
man Tätigkeiten auch einem Threadpool zur Ausführung übergeben
kann. Dann hat lediglich der Threadpool Zugriff auf die ausführenden
Threads und der Benutzer sieht gar nicht, welcher Thread die betreffende
Tätigkeit ausführt. Damit hat der Benutzer auch gar keine
Chance, zur Fehlerbehandlung einen Uncaught-Exception-Handler in den ausführenden
Thread einzuhängen. Dann bleibt nur der Weg, die Fehlerbehandlung
an der einzelnen Tätigkeit aufzuhängen. Das ist auch sonst, unabhängig
von der Verwendung eine Thread-Pools, sinnvoll, weil es meistens sowieso
einen Programmteil gibt, der sich um die Beendigung und das Ergebnis der
Tätigkeit kümmert. Dieser Programmteil kann sich dann auch
um das Scheitern der Tätigkeit per Runtime Exception (oder Error)
kümmern.
ZusammenfassungFür das Ausführen von Threads sind in Java 5.0 neue Abstraktionen zum JDK hinzugekommen. Es gibt das ergebnisproduzierende Callable als Alternative zum ergebnislosen Runnable. Es gibt das Future als zentrale Abstraktion für das Abholen des Resultats oder der Exceptions, die aus einer Tätigkeit entstehen. Wie man Tätigkeiten nun in Threads zur Ausführung bringt, betrachten wir im nächsten Beitrag, der sich den Threadpools im JDK 5.0 widmet.Literaturverweise
|
|||||||||||||||||||
© Copyright 1995-2008 by Angelika Langer. All Rights Reserved. URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/19.Callables/19.Callables.html> last update: 26 Nov 2008 |