Exécutable 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 et Callable est une version améliorée de Runnable qui a été ajoutée à Java 1.5.

Dans cet article, nous explorerons 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 qui peut être exécutée par plusieurs threads. Les tâches exécutables peuvent être exécutées à l'aide de la classe Thread ou ExecutorService, tandis que Callables ne peut être exécutée qu'en utilisant ce dernier.

3. Valeurs de retour

Examinons plus en détail la façon dont ces interfaces gèrent les valeurs de retour.

3.1. Avec Runnable

L' interface Runnable est une interface fonctionnelle et possède une seule méthode run () qui n'accepte aucun paramètre et ne renvoie aucune valeur.

Cela convient aux situations où nous ne recherchons pas un résultat de l'exécution du thread, par exemple, la journalisation des é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. Il n'y a aucune valeur renvoyée par la tâche; la tâche peut être lancée à l'aide d' ExecutorService:

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

Dans ce cas, l' objet Future ne contiendra aucune valeur.

3.2. Avec Callable

L' interface Callable est une interface générique contenant une seule méthode call () - qui renvoie une valeur générique V :

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

Jetons un coup d'œ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éthode call () est renvoyé dans un objet Future :

@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. Avec Runnable

Étant donné que 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. Avec Callable

La méthode call () de Callable contient la clause «throws Exception» 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'un appelable à l'aide d' un ExecutorService, les exceptions sont collectées dans l' objet Future , qui peut être vérifié en appelant la méthode Future.get () . Cela lèvera une ExecutionException - 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, l' exception ExecutionException est lancée lorsque nous transmettons un numéro non valide. Nous pouvons appeler la méthode getCause () sur cet objet d'exception pour obtenir l'exception vérifiée d'origine.

Si nous ne faisons pas l'appel à la méthode get () de la classe Future - alors l'exception levée par la méthode call () ne sera pas signalé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 interfaces Runnable et Callable .

Comme toujours, le code complet de cet article est disponible à l'adresse over sur GitHub.