ExecutorService – Attendre la fin des threads

1. Vue d’ensemble

Le framework ExecutorService facilite le traitement des tâches dans plusieurs threads. Nous allons illustrer certains scénarios dans lesquels nous attendons que les threads terminent leur exécution.

Nous montrerons également comment arrêter correctement un ExecutorService et attendre que les threads déjà en cours d’exécution terminent leur exécution.

2. Le shutdown() de l’executor

Lors de l’utilisation d’un Executor, nous pouvons l’arrêter en appelant les méthodes shutdown() ou shutdownNow(). Cependant, il n’attendra pas que tous les threads cessent de s’exécuter.

Attendre que les threads existants terminent leur exécution peut être réalisé en utilisant la méthode awaitTermination().

Cela bloque le thread jusqu’à ce que toutes les tâches terminent leur exécution ou que le délai d’attente spécifié soit atteint :

3. Le countDownLatch

Voici une autre approche pour résoudre ce problème – en utilisant un CountDownLatch pour signaler l’achèvement d’une tâche.

Nous pouvons l’initialiser avec une valeur qui représente le nombre de fois qu’il peut être décrémenté avant que tous les threads, qui ont appelé la méthode await(), soient notifiés.

Par exemple, si nous avons besoin que le thread actuel attende que N autres threads terminent leur exécution, nous pouvons initialiser le verrou en utilisant N :

4. La méthode invokeAll()

La première approche que nous pouvons utiliser pour exécuter des threads est la méthode invokeAll(). La méthode renvoie une liste d’objets Future après la fin de toutes les tâches ou l’expiration du délai d’attente.

De plus, nous devons noter que l’ordre des objets Future renvoyés est le même que la liste des objets Callable fournis :

5. L’ExecutorCompletionService

Une autre approche pour exécuter plusieurs threads consiste à utiliser ExecutorCompletionService. Il utilise un ExecutorService fourni pour exécuter des tâches.

Une différence par rapport à invokeAll() est l’ordre dans lequel les Futures, représentant les tâches exécutées, sont retournés. ExecutorCompletionService utilise une file d’attente pour stocker les résultats dans l’ordre dans lequel ils se sont terminés, tandis que invokeAll() renvoie une liste ayant le même ordre séquentiel que celui produit par l’itérateur pour une liste de tâches donnée :

Les résultats sont accessibles à l’aide de la méthode take() :

6. Conclusion

Selon le cas d’utilisation, nous avons différentes options pour attendre que les threads terminent leur exécution.

Un CountDownLatch est utile lorsque nous avons besoin d’un mécanisme pour notifier à un ou plusieurs threads qu’un ensemble d’opérations effectuées par d’autres threads est terminé.

ExecutorCompletionService est utile lorsque nous devons accéder au résultat de la tâche dès que possible et d’autres approches lorsque nous voulons attendre la fin de toutes les tâches en cours.