Runnable vs. Callable in Java

1. Übersicht

Seit den Anfängen von Java ist Multithreading ein wichtiger Aspekt der Sprache. Runnable ist die Kernschnittstelle für die Darstellung von Multithread-Aufgaben. Callable ist eine verbesserte Version von Runnable , die in Java 1.5 hinzugefügt wurde.

In diesem Artikel werden die Unterschiede und Anwendungen beider Schnittstellen untersucht.

2. Ausführungsmechanismus

Beide Schnittstellen stellen eine Aufgabe dar, die von mehreren Threads ausgeführt werden kann. Ausführbare Aufgaben können mit der Thread- Klasse oder ExecutorService ausgeführt werden, während Callables nur mit letzterer ausgeführt werden können.

3. Rückgabewerte

Schauen wir uns genauer an, wie diese Schnittstellen mit Rückgabewerten umgehen.

3.1. Mit Runnable

Die Runnable- Schnittstelle ist eine funktionale Schnittstelle und verfügt über eine einzelne run () -Methode, die keine Parameter akzeptiert und keine Werte zurückgibt .

Dies ist für Situationen geeignet, in denen wir nicht nach einem Ergebnis der Thread-Ausführung suchen, z. B. nach der Protokollierung eingehender Ereignisse:

public interface Runnable { public void run(); }

Lassen Sie uns dies anhand eines Beispiels verstehen:

public class EventLoggingTask implements Runnable{ private Logger logger = LoggerFactory.getLogger(EventLoggingTask.class); @Override public void run() { logger.info("Message"); } }

In diesem Beispiel liest der Thread nur eine Nachricht aus der Warteschlange und protokolliert sie in einer Protokolldatei. Es wird kein Wert von der Aufgabe zurückgegeben. Die Aufgabe kann mit ExecutorService gestartet werden :

public void executeTask() { executorService = Executors.newSingleThreadExecutor(); Future future = executorService.submit(new EventLoggingTask()); executorService.shutdown(); }

In diesem Fall enthält das Future- Objekt keinen Wert.

3.2. Mit Callable

Die Callable- Schnittstelle ist eine generische Schnittstelle, die eine einzelne call () -Methode enthält, die einen generischen Wert V zurückgibt :

public interface Callable { V call() throws Exception; }

Schauen wir uns die Berechnung der Fakultät einer Zahl an:

public class FactorialTask implements Callable { int number; // standard constructors public Integer call() throws InvalidParamaterException { int fact = 1; // ... for(int count = number; count > 1; count--) { fact = fact * count; } return fact; } }

Das Ergebnis der call () -Methode wird in einem Future- Objekt zurückgegeben:

@Test public void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future future = executorService.submit(task); assertEquals(120, future.get().intValue()); }

4. Ausnahmebehandlung

Mal sehen, wie geeignet sie für die Ausnahmebehandlung sind.

4.1. Mit Runnable

Da für die Methodensignatur nicht die angegebene "throw" -Klausel angegeben ist,Es gibt keine Möglichkeit, weitere geprüfte Ausnahmen weiterzugeben.

4.2. Mit Callable

Die call () -Methode von Callable enthält die Klausel " throw Exception" , damit wir geprüfte Ausnahmen problemlos weiter verbreiten können:

public class FactorialTask implements Callable { // ... public Integer call() throws InvalidParamaterException { if(number < 0) { throw new InvalidParamaterException("Number should be positive"); } // ... } }

Wenn ein Callable mit einem ExecutorService ausgeführt wird, werden die Ausnahmen im Future- Objekt gesammelt , das durch Aufrufen der Future.get () -Methode überprüft werden kann . Dadurch wird eine ExecutionException ausgelöst, die die ursprüngliche Ausnahme umschließt:

@Test(expected = ExecutionException.class) public void whenException_ThenCallableThrowsIt() { FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); Integer result = future.get().intValue(); }

Im obigen Test wird die ExecutionException ausgelöst, wenn eine ungültige Nummer übergeben wird. Wir können die Methode getCause () für dieses Ausnahmeobjekt aufrufen , um die ursprünglich überprüfte Ausnahme abzurufen .

Wenn wir die get () -Methode der Future- Klasse nicht aufrufen, wird die von der call () -Methode ausgelöste Ausnahme nicht zurückgemeldet und die Aufgabe wird weiterhin als erledigt markiert:

@Test public void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future future = executorService.submit(task); assertEquals(false, future.isDone()); }

Der obige Test wird erfolgreich bestanden, obwohl wir eine Ausnahme für die negativen Werte des Parameters für FactorialCallableTask ausgelöst haben .

5. Schlussfolgerung

In diesem Artikel haben wir die Unterschiede zwischen den Runnable- und Callable- Schnittstellen untersucht.

Wie immer ist der vollständige Code für diesen Artikel auf GitHub verfügbar.