web-dev-qa-db-fra.com

Comment attendre que tous les threads soient terminés, en utilisant ExecutorService?

Je dois exécuter 4 tâches à la fois, comme ceci:

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
    taskExecutor.execute(new MyTask());
}
//...wait for completion somehow

Comment puis-je être averti une fois que tous sont terminés? Pour l'instant, je ne peux penser à rien de mieux que de définir un compteur de tâches global et de le diminuer à la fin de chaque tâche, puis de surveiller en boucle infinie ce compteur pour qu'il devienne 0; ou obtenez une liste des contrats à terme et en boucle infinie moniteur isDone pour chacun d'eux. Quelles sont les meilleures solutions n'impliquant pas de boucles infinies?

Merci.

352
serg

Fondamentalement sur un ExecutorService vous appelez shutdown() puis awaitTermination() :

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}
taskExecutor.shutdown();
try {
  taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
  ...
}
417
cletus

Utilisez un CountDownLatch :

CountDownLatch latch = new CountDownLatch(totalNumberOfTasks);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}

try {
  latch.await();
} catch (InterruptedException E) {
   // handle
}

et dans votre tâche (inclure dans try/finally)

latch.countDown();
164
ChssPly76

ExecutorService.invokeAll() le fait pour vous.

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
List<Callable<?>> tasks; // your tasks
// invokeAll() returns when all tasks are complete
List<Future<?>> futures = taskExecutor.invokeAll(tasks);
81
sjlee

Vous pouvez également utiliser les listes de contrats à terme:

List<Future> futures = new ArrayList<Future>();
// now add to it:
futures.add(executorInstance.submit(new Callable<Void>() {
  public Void call() throws IOException {
     // do something
    return null;
  }
}));

puis, lorsque vous souhaitez joindre toutes les actions, c'est essentiellement l'équivalent de les rejoindre (avec l'avantage supplémentaire de relancer des exceptions des threads enfants vers le principal):

for(Future f: this.futures) { f.get(); }

En gros, l’astuce consiste à appeler .get () sur chaque futur, l’un après l’autre, au lieu de lancer une boucle infinie en appelant isDone () sur (tout ou chacun). Vous êtes donc assuré de "passer" au-delà de ce bloc dès que le dernier thread se termine. La mise en garde est que puisque l'appel de .get () relance des exceptions, si l'un des threads meurt, vous le feriez peut-être avant que les autres threads ne soient terminés (pour éviter cela, vous pouvez ajouter un catch ExecutionException autour du get call]. L’autre inconvénient est qu’il garde une référence à tous les threads. Ainsi, s’ils ont des variables locales de threads, ils ne seront pas collectés tant que vous n’aurez pas dépassé ce bloc (vous pourrez peut-être contourner ce problème, si cela devenait un problème, en supprimant L'avenir est hors de la liste de tableaux). Si vous voulez savoir quel avenir "termine en premier", vous pouvez utiliser quelque chose comme https://stackoverflow.com/a/31885029/3245

43
rogerdpack

En Java8, vous pouvez le faire avec CompletableFuture :

ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
                               .map(task -> CompletableFuture.runAsync(task, es))
                               .toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();    
es.shutdown();
29
AdamSkywalker

Juste mes deux cents. Pour dépasser l'exigence de CountDownLatch de connaître le nombre de tâches à l'avance, vous pouvez le faire à l'ancienne, en utilisant un simple Semaphore.

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
int numberOfTasks=0;
Semaphore s=new Semaphore(0);
while(...) {
    taskExecutor.execute(new MyTask());
    numberOfTasks++;
}

try {
    s.aquire(numberOfTasks);
...

Dans votre tâche, appelez simplement s.release() comme vous le feriez latch.countDown();

25
stryba

La classe CyclicBarrier dans Java 5 et versions ultérieures est conçue pour ce genre de chose.

12
Pekka Enberg

Un peu tard pour le jeu mais pour des raisons de finition ...

Au lieu d'attendre que toutes les tâches soient terminées, vous pouvez penser en termes de principe hollywoodien, "ne m'appelle pas, je t'appellerai" - lorsque j'aurai terminé. Je pense que le code résultant est plus élégant ...

Guava propose des outils intéressants pour y parvenir.

Un exemple ::

Envelopper un ExecutorService dans un ListeningExecutorService ::

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));

Soumettez une collection de callables pour l'exécution ::

for (Callable<Integer> callable : callables) {
  ListenableFuture<Integer> lf = service.submit(callable);
  // listenableFutures is a collection
  listenableFutures.add(lf)
});

Maintenant l'essentiel:

ListenableFuture<List<Integer>> lf = Futures.successfulAsList(listenableFutures);

Joignez un rappel à ListenableFuture, que vous pouvez utiliser pour être averti lorsque tous les futurs sont terminés:

        Futures.addCallback(lf, new FutureCallback<List<Integer>>() {
        @Override
        public void onSuccess(List<Integer> result) {
            log.info("@@ finished processing {} elements", Iterables.size(result));
            // do something with all the results
        }

        @Override
        public void onFailure(Throwable t) {
            log.info("@@ failed because of :: {}", t);
        }
    });

Cela offre également l’avantage de pouvoir rassembler tous les résultats au même endroit une fois le traitement terminé ...

Plus d'informations ici

12

Suivez l'une des approches ci-dessous.

  1. Parcourez toutes les tâches futures , renvoyées de submit sur ExecutorService et vérifiez l'état avec l'appel bloquant get() sur Future objet suggéré par Kiran
  2. Utilisez invokeAll() sur ExecutorService
  3. CountDownLatch
  4. ForkJoinPool ou Executors.html # newWorkStealingPool
  5. Utilisez les API shutdown, awaitTermination, shutdownNow de ThreadPoolExecutor dans l'ordre approprié

Questions SE connexes:

Comment CountDownLatch est-il utilisé dans Java Multithreading?

Comment arrêter correctement Java ExecutorService

7
Ravindra babu

voici deux options, juste un peu confondre lequel est le meilleur aller.

Option 1:

ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
                               .map(task -> CompletableFuture.runAsync(task, es))
                               .toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();    
es.shutdown();

Option 2:

ExecutorService es = Executors.newFixedThreadPool(4);
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
    futures.add(es.submit(task));
}

for(Future<?> future : futures) {
    try {
        future.get();
    }catch(Exception e){
        // do logging and nothing else
    }
}
es.shutdown();

Ici, mettre future.get (); dans try catch c'est une bonne idée, non?

5
user2862544

Vous pouvez envelopper vos tâches dans un autre exécutable, qui enverra des notifications:

taskExecutor.execute(new Runnable() {
  public void run() {
    taskStartedNotification();
    new MyTask().run();
    taskFinishedNotification();
  }
});
5
Zed

Juste pour proposer plus d’alternatives différentes d’utiliser des verrous/barrières. Vous pouvez également obtenir les résultats partiels jusqu'à ce qu'ils finissent tous par CompletionService .

De Java Concurrence dans la pratique: "Si vous avez un lot de calculs à soumettre à un exécuteur et que vous souhaitez récupérer leurs résultats au fur et à mesure de leur disponibilité, vous pouvez conserver l'avenir associé à chaque tâche et interroger terminer en appelant get avec un timeout égal à zéro, ce qui est possible, mais fastidieux . Heureusement, il existe un meilleur moyen : un service d'achèvement. "

Ici la mise en oeuvre

public class TaskSubmiter {
    private final ExecutorService executor;
    TaskSubmiter(ExecutorService executor) { this.executor = executor; }
    void doSomethingLarge(AnySourceClass source) {
        final List<InterestedResult> info = doPartialAsyncProcess(source);
        CompletionService<PartialResult> completionService = new ExecutorCompletionService<PartialResult>(executor);
        for (final InterestedResult interestedResultItem : info)
            completionService.submit(new Callable<PartialResult>() {
                public PartialResult call() {
                    return InterestedResult.doAnOperationToGetPartialResult();
                }
        });

    try {
        for (int t = 0, n = info.size(); t < n; t++) {
            Future<PartialResult> f = completionService.take();
            PartialResult PartialResult = f.get();
            processThisSegment(PartialResult);
            }
        } 
        catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } 
        catch (ExecutionException e) {
            throw somethinghrowable(e.getCause());
        }
    }
}
3
Alberto Gurrion

Je viens d'écrire un exemple de programme qui résout votre problème. Aucune mise en œuvre concise n'a été donnée, je vais donc en ajouter une. Bien que vous puissiez utiliser executor.shutdown() et executor.awaitTermination(), ce n'est pas la meilleure pratique car le temps pris par différents threads serait imprévisible.

ExecutorService es = Executors.newCachedThreadPool();
    List<Callable<Integer>> tasks = new ArrayList<>();

    for (int j = 1; j <= 10; j++) {
        tasks.add(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                int sum = 0;
                System.out.println("Starting Thread "
                        + Thread.currentThread().getId());

                for (int i = 0; i < 1000000; i++) {
                    sum += i;
                }

                System.out.println("Stopping Thread "
                        + Thread.currentThread().getId());
                return sum;
            }

        });
    }

    try {
        List<Future<Integer>> futures = es.invokeAll(tasks);
        int flag = 0;

        for (Future<Integer> f : futures) {
            Integer res = f.get();
            System.out.println("Sum: " + res);
            if (!f.isDone()) 
                flag = 1;
        }

        if (flag == 0)
            System.out.println("SUCCESS");
        else
            System.out.println("FAILED");

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
3
Kiran

Ceci est ma solution, basée sur "AdamSkywalker" astuce, et cela fonctionne

package frss.main;

import Java.util.ArrayList;
import Java.util.List;
import Java.util.concurrent.CompletableFuture;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;

public class TestHilos {

    void procesar() {
        ExecutorService es = Executors.newFixedThreadPool(4);
        List<Runnable> tasks = getTasks();
        CompletableFuture<?>[] futures = tasks.stream().map(task -> CompletableFuture.runAsync(task, es)).toArray(CompletableFuture[]::new);
        CompletableFuture.allOf(futures).join();
        es.shutdown();

        System.out.println("FIN DEL PROCESO DE HILOS");
    }

    private List<Runnable> getTasks() {
        List<Runnable> tasks = new ArrayList<Runnable>();

        Hilo01 task1 = new Hilo01();
        tasks.add(task1);

        Hilo02 task2 = new Hilo02();
        tasks.add(task2);
        return tasks;
    }

    private class Hilo01 extends Thread {

        @Override
        public void run() {
            System.out.println("HILO 1");
        }

    }

    private class Hilo02 extends Thread {

        @Override
        public void run() {
            try {
                sleep(2000);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("HILO 2");
        }

    }


    public static void main(String[] args) {
        TestHilos test = new TestHilos();
        test.procesar();
    }
}
2
frss-soft.com

Vous pouvez utiliser ce code:

public class MyTask implements Runnable {

    private CountDownLatch countDownLatch;

    public MyTask(CountDownLatch countDownLatch {
         this.countDownLatch = countDownLatch;
    }

    @Override
    public void run() {
         try {
             //Do somethings
             //
             this.countDownLatch.countDown();//important
         } catch (InterruptedException ex) {
              Thread.currentThread().interrupt();
         }
     }
}

CountDownLatch countDownLatch = new CountDownLatch(NUMBER_OF_TASKS);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
for (int i = 0; i < NUMBER_OF_TASKS; i++){
     taskExecutor.execute(new MyTask(countDownLatch));
}
countDownLatch.await();
System.out.println("Finish tasks");
2
Tuan Pham

J'ai créé l'exemple de travail suivant. L'idée est de pouvoir traiter un groupe de tâches (j'utilise une file d'attente par exemple) avec de nombreux threads (déterminés par le nombre numberOfTasks/threshold) et d'attendre que toutes les threads soient terminés pour poursuivre un autre traitement.

import Java.util.PriorityQueue;
import Java.util.Queue;
import Java.util.concurrent.CountDownLatch;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;

/** Testing CountDownLatch and ExecutorService to manage scenario where
 * multiple Threads work together to complete tasks from a single
 * resource provider, so the processing can be faster. */
public class ThreadCountDown {

private CountDownLatch threadsCountdown = null;
private static Queue<Integer> tasks = new PriorityQueue<>();

public static void main(String[] args) {
    // Create a queue with "Tasks"
    int numberOfTasks = 2000;
    while(numberOfTasks-- > 0) {
        tasks.add(numberOfTasks);
    }

    // Initiate Processing of Tasks
    ThreadCountDown main = new ThreadCountDown();
    main.process(tasks);
}

/* Receiving the Tasks to process, and creating multiple Threads
* to process in parallel. */
private void process(Queue<Integer> tasks) {
    int numberOfThreads = getNumberOfThreadsRequired(tasks.size());
    threadsCountdown = new CountDownLatch(numberOfThreads);
    ExecutorService threadExecutor = Executors.newFixedThreadPool(numberOfThreads);

    //Initialize each Thread
    while(numberOfThreads-- > 0) {
        System.out.println("Initializing Thread: "+numberOfThreads);
        threadExecutor.execute(new MyThread("Thread "+numberOfThreads));
    }

    try {
        //Shutdown the Executor, so it cannot receive more Threads.
        threadExecutor.shutdown();
        threadsCountdown.await();
        System.out.println("ALL THREADS COMPLETED!");
        //continue With Some Other Process Here
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

/* Determine the number of Threads to create */
private int getNumberOfThreadsRequired(int size) {
    int threshold = 100;
    int threads = size / threshold;
    if( size > (threads*threshold) ){
        threads++;
    }
    return threads;
}

/* Task Provider. All Threads will get their task from here */
private synchronized static Integer getTask(){
    return tasks.poll();
}

/* The Threads will get Tasks and process them, while still available.
* When no more tasks available, the thread will complete and reduce the threadsCountdown */
private class MyThread implements Runnable {

    private String threadName;

    protected MyThread(String threadName) {
        super();
        this.threadName = threadName;
    }

    @Override
    public void run() {
        Integer task;
        try{
            //Check in the Task pool if anything pending to process
            while( (task = getTask()) != null ){
                processTask(task);
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }finally {
            /*Reduce count when no more tasks to process. Eventually all
            Threads will end-up here, reducing the count to 0, allowing
            the flow to continue after threadsCountdown.await(); */
            threadsCountdown.countDown();
        }
    }

    private void processTask(Integer task){
        try{
            System.out.println(this.threadName+" is Working on Task: "+ task);
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }
}
}

J'espère que ça aide!

2
Fernando Gil

Java 8 - Nous pouvons utiliser une API de flux pour traiter un flux. S'il vous plaît voir extrait ci-dessous

final List<Runnable> tasks = ...; //or any other functional interface
tasks.stream().parallel().forEach(Runnable::run) // Uses default pool

//alternatively to specify parallelism 
new ForkJoinPool(15).submit(
          () -> tasks.stream().parallel().forEach(Runnable::run) 
    ).get();
1
Vlad

vous devriez utiliser la méthode executorService.shutdown() et executorService.awaitTermination.

Un exemple comme suit:

public class ScheduledThreadPoolExample {

    public static void main(String[] args) throws InterruptedException {
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        executorService.scheduleAtFixedRate(() -> System.out.println("process task."),
                0, 1, TimeUnit.SECONDS);

        TimeUnit.SECONDS.sleep(10);
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.DAYS);
    }

}
1
Rollen Holt

Donc, je poste ma réponse à partir de la question liée ici, au cas où quelqu'un veut un moyen plus simple de le faire

ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture[] futures = new CompletableFuture[10];
int i = 0;
while (...) {
    futures[i++] =  CompletableFuture.runAsync(runner, executor);
}

CompletableFuture.allOf(futures).join(); // THis will wait until all future ready.

Vous pouvez utiliser votre propre sous-classe de ExecutorCompletionService pour envelopper taskExecutor et votre propre implémentation de BlockingQueue pour être informé de l'achèvement de chaque tâche et effectuer tout rappel ou toute autre action. vous désirez lorsque le nombre de tâches terminées atteint votre objectif souhaité.

1
Alex Martelli

ExecutorService WORKER_THREAD_POOL 
  = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(2);
for (int i = 0; i < 2; i++) {
    WORKER_THREAD_POOL.submit(() -> {
        try {
            // doSomething();
            latch.countDown();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });
}

// wait for the latch to be decremented by the two remaining threads
latch.await();

Si doSomething() renvoie d'autres exceptions, la latch.countDown() semble ne pas s'exécuter, que dois-je faire?

0
Pengfei Zhan