Runnable vs. Callable in Java

Übersicht

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

In diesem Artikel werden wir die Unterschiede und die Anwendungen beider Schnittstellen untersuchen.

Ausführungsmechanismus

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

Rückgabewerte

Werfen wir einen genaueren Blick auf die Art und Weise, wie diese Schnittstellen Rückgabewerte verarbeiten.

3.1. Mit Runnable

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

Dies eignet sich für Situationen, in denen wir nicht nach einem Ergebnis der Thread-Ausführung suchen, z. B. bei 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. 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 zukünftige Objekt keinen Wert.

3.2. Mit Callable

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

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

Werfen wir einen Blick auf die Berechnung der Fakultät einer Zahl:

public class FactorialTask implements Callable<Integer> { 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 zukünftigen Objekt zurückgegeben:

@Testpublic void whenTaskSubmitted_ThenFutureResultObtained(){ FactorialTask task = new FactorialTask(5); Future<Integer> future = executorService.submit(task); assertEquals(120, future.get().intValue());}

Ausnahmebehandlung

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

4.1. Mit Runnable

Da in der Methodensignatur die Klausel „throws“ nicht angegeben ist, gibt es keine Möglichkeit, weitere geprüfte Ausnahmen weiterzugeben.

4.2. Mit Callable

Die call() -Methode von Callable enthält die Klausel „throws Exception“, sodass wir geprüfte Ausnahmen problemlos weiterleiten können:

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

Beim Ausführen eines aufrufbaren Objekts mit einem ExecutorService werden die Ausnahmen im Future Objekt gesammelt, das durch einen Aufruf der Future überprüft werden kann.get() -Methode. 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<Integer> future = executorService.submit(task); Integer result = future.get().intValue();}

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

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

@Testpublic void whenException_ThenCallableDoesntThrowsItIfGetIsNotCalled(){ FactorialCallableTask task = new FactorialCallableTask(-5); Future<Integer> 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 an FactorialCallableTask .

Fazit

In diesem Artikel haben wir die Unterschiede zwischen der ausführbaren und der aufrufbaren Schnittstelle untersucht.

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

Erste Schritte mit Spring 5 und Spring Boot 2 über den Learn Spring-Kurs:

>> SCHAUEN SIE SICH DEN KURS AN

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.

Previous post Nitril
Next post Julian Morris auf ‚New Girl‘ = Beste Idee aller Zeiten