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
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 Future
s 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 Future
s 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é.
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.
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 ).
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.
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é.
Juste utiliser
latch = new CountDownLatch(noThreads)
Dans chaque fil
latch.countDown();
et comme barrière
latch.await();
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
.
Parcourez toutes les tâches Future
de submit
sur ExecutorService
et vérifiez l'état avec l'appel bloquant get()
sur Future
objet
Utilisation de invokeAll sur ExecutorService
Utilisation de CountDownLatch
Utilisation de ForkJoinPool ou newWorkStealingPool sur Executors
(depuis Java 8)
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.
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";
}
}
}
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!
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
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
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.");
}
Une solution simple consiste à utiliser des threads avec join. Référez-vous: Rejoindre des discussions
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.