Was passiert, wenn ein Task im Executor eine Exception wirft?

Melden
  1. Einführung
  2. Behandlung von Exceptions bei Runnable-Tasks
  3. Behandlung von Exceptions bei Callable-Tasks
  4. UncaughtExceptionHandler und Logging
  5. Zusammenfassung

Einführung

In Java werden Aufgaben, die nebenläufig ausgeführt werden sollen, häufig in einem Executor oder ExecutorService eingereicht. Dabei handelt es sich um eine abstrakte Verwaltung von Threads, die die Ausführung von Runnable- oder Callable-Objekten übernimmt. Wenn dabei ein Task (Runnable oder Callable) eine Exception wirft, ist es wichtig zu verstehen, wie diese Exceptions behandelt werden, da dies Einfluss auf Fehlererkennung und Programmverhalten hat.

Behandlung von Exceptions bei Runnable-Tasks

Wenn ein Task als Runnable eingereicht wird und in der run()-Methode eine Exception—egal ob RuntimeException oder eine andere Form von unchecked Exception—auftritt, führt das dazu, dass diese Exception im Thread, der vom Executor verwaltet wird, unbehandelt bleibt. Da Runnable.run() keine Exceptions deklariert, kann die Exception nicht propagiert werden. Das Resultat ist häufig, dass die Exception in der Thread-Umgebung verschluckt wird, in manchen Implementierungen aber vom Executor-Framework oder der JVM protokolliert wird (z.B. durch Ausgabe auf der Konsole oder in einem Logger mittels UncaughtExceptionHandler).

Wichtig ist, dass die Exception den Executor in der Regel nicht dazu veranlasst, die Ausführung anderer Tasks abzubrechen oder den Executor zu stoppen. Der Executor-Thread beendet nur den aktuellen Runnable, und der Threadpool steht weiterhin für weitere Aufgaben zur Verfügung.

Behandlung von Exceptions bei Callable-Tasks

Wird ein Task als Callable eingereicht, erwartet man normalerweise ein Ergebnis oder eine Exception als Rückmeldung. Hier wird die Exception im Gegensatz zu Runnable nicht geschluckt. Wenn der Callable eine Exception wirft, so wird diese beim Aufruf von Future.get() als ExecutionException eingehüllt und weitergegeben.

Das bedeutet, dass man mit Callable-Tasks Exceptions deutlich besser behandeln kann, weil die Methode Future.get() blockiert, bis der Task abgeschlossen ist, und dann entweder das Ergebnis liefert oder die aufgetretene Exception an den Aufrufer weiterreicht. So kann der Caller entscheiden, wie mit dem Fehler umgegangen wird.

UncaughtExceptionHandler und Logging

Für Tasks, die Runnable implementieren, kann man bei Bedarf auf den Mechanismus der Thread.UncaughtExceptionHandler zurückgreifen. Dieser ermöglicht es, Exceptions, die innerhalb eines Threads ungehandelt aufgetreten sind, zentral zu erfassen und zu protokollieren. Einige Executor-Implementierungen erlauben es, eigene ThreadFactorys zu setzen, mit denen man Threads mit einem vordefinierten UncaughtExceptionHandler erzeugen kann.

Zusammenfassung

Wenn ein Task im Executor eine Exception wirft, bedeutet dies je nach Art des Tasks und Nutzungsform unterschiedliche Handhabung: Bei Runnable wird die Exception in der Regel verschluckt und nicht direkt an den Aufrufer weitergeleitet, es sei denn, es existiert ein UncaughtExceptionHandler. Bei Callable wird die Exception beim Abfragen des Ergebnisses über die Future als ExecutionException weitergereicht. Der Executor selbst oder seine Threads brechen durch eine Task-Exception normalerweise nicht ab. Um Exceptions zuverlässig zu erfassen, sollte man entweder Callable-Tasks verwenden, die Exceptions explizit behandeln, oder für Runnable-Tasks geeignete Fehlerbehandlungs- und Loggingmechanismen implementieren.

0

Kommentare