Runnable vs Callable en Java

Runnable vs. Callable en Java

1. Vue d'ensemble

Depuis les débuts de Java, le multithreading est un aspect majeur du langage. Runnable est l'interface principale fournie pour représenter les tâches multithreads etCallable est une version améliorée deRunnable qui a été ajoutée dans Java 1.5.

Dans cet article, nous allons explorer les différences et les applications des deux interfaces.

2. Mécanisme d'exécution

Les deux interfaces sont conçues pour représenter une tâche pouvant être exécutée par plusieurs threads. Les tâchesRunnable peuvent être exécutées en utilisant la classeThread ouExecutorService alors queCallables ne peut être exécutée qu'en utilisant cette dernière.

3. Valeurs de retour

Examinons de plus près la manière dont ces interfaces gèrent les valeurs de retour.

3.1. AvecRunnable

L’interfaceRunnable est une interface fonctionnelle et possède une seule méthoderun() qui n’accepte aucun paramètre et ne renvoie aucune valeur.

Cela convient aux situations dans lesquelles nous ne cherchons pas de résultat de l'exécution du thread, par exemple la journalisation d'événements entrants:

public interface Runnable {
    public void run();
}

Comprenons cela avec un exemple:

public class EventLoggingTask implements  Runnable{
    private Logger logger
      = LoggerFactory.getLogger(EventLoggingTask.class);

    @Override
    public void run() {
        logger.info("Message");
    }
}

Dans cet exemple, le thread lira simplement un message de la file d'attente et le consignera dans un fichier journal. Aucune valeur n'est renvoyée par la tâche; la tâche peut être lancée en utilisantExecutorService:

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

Dans ce cas, l'objetFuture ne contiendra aucune valeur.

3.2. AvecCallable

L'interfaceCallable est une interface générique contenant une seule méthodecall() - qui renvoie une valeur génériqueV:

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

Jetons un œil au calcul de la factorielle d'un nombre:

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;
    }
}

Le résultat de la méthodecall() est renvoyé dans un objetFuture:

@Test
public void whenTaskSubmitted_ThenFutureResultObtained(){
    FactorialTask task = new FactorialTask(5);
    Future future = executorService.submit(task);

    assertEquals(120, future.get().intValue());
}

4. Gestion des exceptions

Voyons comment ils conviennent pour la gestion des exceptions.

4.1. AvecRunnable

Puisque la signature de méthode n'a pas la clause «throws» spécifiée, __ il n'y a aucun moyen de propager d'autres exceptions vérifiées.

4.2. AvecCallable

La méthodeCallable’s call() contient «lève la clauseException” afin que nous puissions facilement propager les exceptions vérifiées plus loin:

public class FactorialTask implements Callable {
    // ...
    public Integer call() throws InvalidParamaterException {

        if(number < 0) {
            throw new InvalidParamaterException("Number should be positive");
        }
    // ...
    }
}

Dans le cas de l'exécution d'unCallable using et d'unExecutorService,, les exceptions sont collectées dans l'objetFuture, qui peut être vérifié en faisant un appel à la méthodeFuture.get(). Cela lèvera unExecutionException – qui encapsule l'exception d'origine:

@Test(expected = ExecutionException.class)
public void whenException_ThenCallableThrowsIt() {

    FactorialCallableTask task = new FactorialCallableTask(-5);
    Future future = executorService.submit(task);
    Integer result = future.get().intValue();
}

Dans le test ci-dessus, leExecutionException est lancé lorsque nous transmettons un nombre invalide. Nous pouvons appeler la méthodegetCause() sur cet objet d'exception pour obtenir l'exception vérifiée d'origine.

Si nous ne faisons pas l'appel à la méthodeget() de la classeFuture - alors l'exception levée par la méthodecall() ne sera pas rapportée et la tâche sera toujours marquée comme terminée :

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

    assertEquals(false, future.isDone());
}

Le test ci-dessus réussira même si nous avons levé une exception pour les valeurs négatives du paramètre àFactorialCallableTask.

5. Conclusion

Dans cet article, nous avons exploré les différences entre les interfacesRunnable etCallable.

Comme toujours, le code complet de cet article est disponibleover on GitHub.