Runable vs. Callable in Java

Lauffähig vs. Aufrufbar in Java

1. Überblick

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

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

2. Ausführungsmechanismus

Beide Schnittstellen sollen eine Aufgabe darstellen, die von mehreren Threads ausgeführt werden kann. Runnable Aufgaben können mit der KlasseThread oderExecutorService ausgeführt werden, währendCallables 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. MitRunnable

DieRunnable-Schnittstelle ist eine funktionale Schnittstelle und verfügt über eine einzelnerun()-Methode, die keine Parameter akzeptiert und keine Werte zurückgibt.

Dies eignet sich für Situationen, in denen wir kein Ergebnis der Thread-Ausführung suchen, z. B. die 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 einfach eine Nachricht aus der Warteschlange und protokolliert sie in einer Protokolldatei. Von der Aufgabe wird kein Wert zurückgegeben. Die Aufgabe kann mitExecutorService: gestartet werden

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

In diesem Fall enthält das ObjektFuturekeinen Wert.

3.2. MitCallable

DieCallable-Schnittstelle ist eine generische Schnittstelle, die eine einzelnecall()-Methode enthält, die einen generischen WertV 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 dercall()-Methode wird innerhalb einesFuture-Objekts 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. MitRunnable

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

4.2. MitCallable

Die Methode vonCallable’s call()enthält die Klausel "throwException”", 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 einCallable using und einExecutorService, ausgeführt werden, werden die Ausnahmen imFuture-Objekt gesammelt, was durch Aufrufen derFuture.get()-Methode überprüft werden kann. Dies löst einExecutionException – aus, das 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 wirdExecutionException ausgelöst, wenn eine ungültige Zahl übergeben wird. Wir können diegetCause()-Methode für dieses Ausnahmeobjekt aufrufen, um die ursprünglich überprüfte Ausnahme abzurufen.

Wenn wir die Methodeget()der KlasseFuturenicht aufrufen, wird die von der Methodecall()ausgelöste Ausnahme nicht zurückgemeldet und die Aufgabe wird weiterhin als abgeschlossen 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 aufFactorialCallableTask. ausgelöst haben

5. Fazit

In diesem Artikel haben wir die Unterschiede zwischen den SchnittstellenRunnable undCallable untersucht.

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