web-dev-qa-db-fra.com

ExecutorService, comment attendre la fin de toutes les tâches

Quel est le moyen le plus simple d’attendre que toutes les tâches de ExecutorService se terminent? Ma tâche est essentiellement informatique, je souhaite donc exécuter un grand nombre de tâches - une sur chaque cœur. En ce moment, ma configuration ressemble à ceci:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTask implémente runnable. Cela semble exécuter les tâches correctement, mais le code se bloque sur wait() avec IllegalMonitorStateException. C'est étrange, car j'ai joué avec quelques exemples de jouets et cela a semblé fonctionner.

uniquePhrases contient plusieurs dizaines de milliers d'éléments. Devrais-je utiliser une autre méthode? Je cherche quelque chose d'aussi simple que possible

182
george smiley

La méthode la plus simple consiste à utiliser ExecutorService.invokeAll() qui fait ce que vous voulez dans une ligne. Dans votre langage, vous devrez modifier ou encapsuler ComputeDTask pour implémenter Callable<>, ce qui peut vous donner un peu plus de flexibilité. Probablement dans votre application, il existe une implémentation significative de Callable.call(), mais voici un moyen de l'envelopper si vous n'utilisez pas Executors.callable() .

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

Comme d'autres l'ont fait remarquer, vous pouvez utiliser la version de délai d'expiration de invokeAll() si nécessaire. Dans cet exemple, answers va contenir un groupe de Futures qui renverront des valeurs null (voir la définition de Executors.callable(). Ce que vous voulez probablement faire est probablement une légère refactorisation afin de pouvoir obtenir une réponse utile, ou une référence au sous-jacent ComputeDTask, mais je ne peux pas le dire à partir de votre exemple.

Si ce n'est pas clair, notez que invokeAll() ne sera renvoyé que lorsque toutes les tâches seront terminées. (c’est-à-dire que tous les Futures de votre collection answers signaleront .isDone() si on le leur demande.) Ceci évite l’arrêt manuel, la temporisation, etc ... et vous permet de réutiliser ce ExecutorService proprement. pour plusieurs cycles, si désiré.

Il y a quelques questions connexes sur SO:

Aucune de ces questions n’est strictement pertinente pour votre question, mais elles donnent un peu de couleur sur la façon dont les gens pensent que Executor/ExecutorService devrait être utilisé.

199
andersoj

Si vous souhaitez attendre que toutes les tâches soient terminées, utilisez la méthode shutdown au lieu de wait. Ensuite, suivez-le avec awaitTermination .

En outre, vous pouvez utiliser Runtime.availableProcessors pour obtenir le nombre de threads matériels afin que vous puissiez initialiser votre pool de threads correctement.

57
NG.

Si attendre que toutes les tâches du ExecutorService soient terminées n'est pas précisément votre objectif, mais plutôt qu'un lot de tâches spécifique est terminé, vous pouvez utiliser un CompletionService - plus précisément , un ExecutorCompletionService .

L'idée est de créer un ExecutorCompletionService enveloppant votre Executor, soumettre certains nombre conn de tâches à travers le CompletionService, puis dessinez-le même nombre de résultats de le file d'attente d'achèvement ​​en utilisant take() (qui bloque) ou poll() (qui ne fonctionne pas). Une fois que vous avez dessiné tous les résultats attendus correspondant aux tâches que vous avez soumises, vous savez qu'ils sont tous terminés.

Permettez-moi de le dire une fois de plus, parce que l'interface ne l'indique pas clairement: vous devez savoir combien d'éléments vous avez mis dans la variable CompletionService afin de savoir combien d'éléments vous souhaitez extraire. Cela est particulièrement important avec la méthode take(): appelez-la une fois de trop et cela bloquera votre thread appelant jusqu'à ce qu'un autre thread soumette un autre travail au même CompletionService.

Il y a quelques exemples montrant comment utiliser CompletionService dans le livre [Java Concurrency in Practice ).

48
seh

Si vous souhaitez attendre que le service exécuteur ait terminé son exécution, appelez shutdown(), puis waitTermination (units, unitType) , par exemple. awaitTermination(1, MINUTE). ExecutorService ne bloque pas son propre moniteur, vous ne pouvez donc pas utiliser wait etc.

11
mdma

Vous pouvez attendre que les travaux se terminent à un certain intervalle:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

Ou vous pouvez utiliser ExecutorService .submit ( Runnable ) et collectez les objets futurs qu'il renvoie et appelez get () à tour de rôle pour les attendre terminer.

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

InterruptedException est extrêmement important à gérer correctement. C’est ce qui vous permet, à vous ou aux utilisateurs de votre bibliothèque, de terminer un long processus en toute sécurité.

7
Alain O'Dea

Juste utiliser

latch = new CountDownLatch(noThreads)

Dans chaque fil

latch.countDown();

et comme barrière

latch.await();
6
J. Ruhe

Cause fondamentale de IllegalMonitorStateException :

Lancé pour indiquer qu'un thread a tenté d'attendre sur le moniteur d'un objet ou pour notifier d'autres threads en attente sur le moniteur d'un objet sans posséder le moniteur spécifié.

Depuis votre code, vous venez d’appeler wait () sur ExecutorService sans posséder le verrou.

Le code ci-dessous corrigera IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

Suivez l’une des approches ci-dessous pour attendre la fin de toutes les tâches qui ont été soumises à ExecutorService.

  1. Parcourez toutes les tâches Future de submit sur ExecutorService et vérifiez l'état avec l'appel bloquant get() sur Future objet

  2. Utilisation de invokeAll sur ExecutorService

  3. Utilisation de CountDownLatch

  4. Utilisation de ForkJoinPool ou newWorkStealingPool sur Executors (depuis Java 8)

  5. Fermez le pool comme recommandé dans la documentation Oracle page

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }
    

    Si vous souhaitez attendre que toutes les tâches soient terminées lorsque vous utilisez l’option 5 au lieu des options 1 à 4, modifiez

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
    

    à

    a while(condition) qui vérifie toutes les minutes.

5
Ravindra babu

Vous pouvez utiliser la méthode ExecutorService.invokeAll, elle exécutera toutes les tâches et attendra que tous les threads aient terminé leur tâche.

Voici complet javadoc

Vous pouvez également utiliser la version surchargée de cette méthode pour spécifier le délai d'expiration.

Voici un exemple de code avec ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}
5
Nitin Vavdiya

J'ai également la situation que j'ai un ensemble de documents à explorer. Je commence par un document initial qui doit être traité, ce document contient des liens vers d'autres documents qui doivent également être traités, etc.

Dans mon programme principal, je veux juste écrire quelque chose comme ce qui suit, où Crawler contrôle un tas de threads.

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

La même situation se produirait si je voulais naviguer dans un arbre; J'allais au nœud racine, le processeur de chaque nœud ajouterait des enfants à la file d'attente si nécessaire et un groupe de threads traiterait tous les nœuds de l'arborescence jusqu'à ce qu'il n'y en ait plus.

Je n'ai rien trouvé dans la machine virtuelle qui me semble un peu surprenant. J'ai donc écrit une classe AutoStopThreadPool que l'on peut utiliser directement ou sous-classe pour ajouter des méthodes adaptées au domaine, par exemple. schedule(Document). J'espère que ça aide!

Javadoc AutoStopThreadPool | Télécharger

3
Adrian Smith

Ajoutez tous les threads de la collection et soumettez-le à l'aide de invokeAll. Si vous pouvez utiliser la méthode invokeAll de ExecutorService, JVM ne passera pas à la ligne suivante tant que tous les threads ne sont pas terminés.

Voici un bon exemple: invokeAll via ExecutorService

2
zgormez

Soumettez vos tâches dans le Runner , puis patientez en appelant la méthode waitTillDone () comme ça:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

Pour l'utiliser, ajoutez cette dépendance gradle/maven: 'com.github.matejtymes:javafixes:1.0'

Pour plus de détails, regardez ici: https://github.com/MatejTymes/JavaFixes ou ici: http://matejtymes.blogspot.com/2016/04/executor-that-notifies -you-when-task.html

1
Matej Tymes

Je vais simplement attendre que l'exécuteur se termine avec un délai spécifié que vous jugez approprié pour l'exécution des tâches.

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }
0
punkers

Une solution simple consiste à utiliser des threads avec join. Référez-vous: Rejoindre des discussions

0
Vicky Kapadia

On dirait que vous avez besoin de ForkJoinPool et utilisez le pool global pour exécuter des tâches.

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that's it.
}

La beauté est dans pool.awaitQuiescence où la méthode bloquera, utilisera le thread de l'appelant pour exécuter ses tâches, puis retournera lorsqu'il est vraiment vide.

0
adib