ExecutorService - En attente de la fin des threads

ExecutorService - En attente de la fin des threads

1. Vue d'ensemble

Le frameworkExecutorService 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 allons également montrer comment arrêter correctement unExecutorService et attendre que les threads déjà en cours d'exécution terminent leur exécution.

2. Après l'arrêt deExecutor’s

Lorsque vous utilisez unExecutor,, nous pouvons l'arrêter en appelant les méthodesshutdown() oushutdownNow(). Although, it won’t wait until all threads stop executing.

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

Cela bloque le thread jusqu'à ce que toutes les tâches aient terminé leur exécution ou que le délai imparti 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 deCountDownLatch

Examinons ensuite une autre approche pour résoudre ce problème - en utilisant unCountDownLatch 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éthodeawait(), soient notifiés.

Par exemple, si nous avons besoin que le thread actuel attende qu'un autre threadN termine leur exécution, nous pouvons initialiser le verrou en utilisantN:

ExecutorService WORKER_THREAD_POOL
  = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
    WORKER_THREAD_POOL.submit(() -> {
        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 deinvokeAll()

La première approche que nous pouvons utiliser pour exécuter des threads est la méthodeinvokeAll(). The method returns a list of Future objects after all tasks finish or the timeout expires.

De plus, nous devons noter que l'ordre des objetsFuture retournés est le même que la liste des objetsCallable 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 deExecutorCompletionService

Une autre approche pour exécuter plusieurs threads consiste à utiliserExecutorCompletionService.. Il utilise unExecutorService fourni pour exécuter les tâches.

Une différence par rapport àinvokeAll() est l'ordre dans lequel lesFutures, représentant les tâches exécutées sont renvoyés. ExecutorCompletionService uses a queue to store the results in the order they are finished, tandis queinvokeAll() 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éthodetake():

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 < 1000);
LOG.debug("Thread finished after: " + totalProcessingTime
  + " milliseconds");

future = service.take();
String secondThreadResponse = future.get();
totalProcessingTime
  = System.currentTimeMillis() - startProcessingTime;

assertTrue(
  "Last response should be from the slow thread",
  "slow thread".equals(secondThreadResponse));
assertTrue(
  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.

UnCountDownLatch est utile lorsque nous avons besoin d'un mécanisme pour avertir 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 que toutes les tâches en cours soient terminées.

Le code source de l'article est disponibleover on GitHub.