ExecutorService - En attente de 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.

En outre, nous montrerons comment arrêter correctement un ExecutorService et attendre que les threads déjà en cours d'exécution terminent leur exécution.

2. Après l' arrêt de l'exécuteur

Lors de l'utilisation d'un exécuteur, nous pouvons l'arrêter en appelant les méthodes shutdown () ou shutdownNow () . Cependant, il n'attendra pas que tous les threads arrêtent 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 aient terminé leur exécution ou que le délai spécifié soit atteint:

public void awaitTerminationAfterShutdown(ExecutorService threadPool) { threadPool.shutdown(); try { if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { threadPool.shutdownNow(); } } catch (InterruptedException ex) { threadPool.shutdownNow(); Thread.currentThread().interrupt(); } }

3. Utilisation de CountDownLatch

Ensuite, regardons 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 où 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 qu'un autre N threads termine son exécution, nous pouvons initialiser le verrou en utilisant N :

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); CountDownLatch latch = new CountDownLatch(2); for (int i = 0; i  { try { // ... latch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } // wait for the latch to be decremented by the two remaining threads latch.await();

4. Utilisation de invokeAll ()

La première approche que nous pouvons utiliser pour exécuter des threads est la méthode invokeAll () . La méthode retourne une liste d' objets Future une fois toutes les tâches terminées ou le délai d'expiration .

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

ExecutorService WORKER_THREAD_POOL = Executors.newFixedThreadPool(10); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); long startProcessingTime = System.currentTimeMillis(); List
     
       futures = WORKER_THREAD_POOL.invokeAll(callables); awaitTerminationAfterShutdown(WORKER_THREAD_POOL); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue(totalProcessingTime >= 3000); String firstThreadResponse = futures.get(0).get(); assertTrue("fast thread".equals(firstThreadResponse)); String secondThreadResponse = futures.get(1).get(); assertTrue("slow thread".equals(secondThreadResponse));
     
    

5. Utilisation d' 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 sont terminés , tandis que invokeAll () renvoie une liste ayant le même ordre séquentiel que celui produit par l'itérateur pour la liste de tâches donnée:

CompletionService service = new ExecutorCompletionService(WORKER_THREAD_POOL); List
    
      callables = Arrays.asList( new DelayedCallable("fast thread", 100), new DelayedCallable("slow thread", 3000)); for (Callable callable : callables) { service.submit(callable); } 
    

Les résultats sont accessibles en utilisant la méthode take () :

long startProcessingTime = System.currentTimeMillis(); Future future = service.take(); String firstThreadResponse = future.get(); long totalProcessingTime = System.currentTimeMillis() - startProcessingTime; assertTrue("First response should be from the fast thread", "fast thread".equals(firstThreadResponse)); assertTrue(totalProcessingTime >= 100 && totalProcessingTime = 3000 && totalProcessingTime < 4000); LOG.debug("Thread finished after: " + totalProcessingTime + " milliseconds"); awaitTerminationAfterShutdown(WORKER_THREAD_POOL);

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 d'exécution.

Le code source de l'article est disponible à l'adresse over sur GitHub.