Exécutorcompletionservice? Pourquoi en avoir besoin si nous en avons tous?
si nous utilisons un ExecutorCompletionService nous pouvons soumettre une série de tâches comme Callable
s et obtenir le résultat en interaction avec le CompletionService
comme un queue
.
Mais il y a aussi le invokeAll
de ExecutorService
qui accepte un Collection
de tâches et nous obtenons une liste de Future
pour récupérer les résultats.
autant que je puisse dire, il n'y a aucun avantage à utiliser l'un ou l'autre (sauf que nous évitons une boucle for
en utilisant un invokeAll
que nous aurions à submit
les tâches au CompletionService
) et essentiellement ils sont la même idée avec une légère différence.
alors pourquoi y a-t-il deux façons différentes de soumettre une série de tâches? Ai-je raison de dire que les performances sont équivalentes? Est-il un cas que l'un est plus adapté que les autres? Je ne peux pas penser à un.
4 réponses
en utilisant un ExecutorCompletionService.poll/take
, vous recevez le Future
s comme ils terminent, dans l'ordre d'achèvement (plus ou moins). En utilisant ExecutorService.invokeAll
, vous n'avez pas ce pouvoir; soit vous bloquez jusqu'à ce que tout soit terminé, soit vous spécifiez un délai après lequel les incomplets sont annulés.
static class SleepingCallable implements Callable<String> {
final String name;
final long period;
SleepingCallable(final String name, final long period) {
this.name = name;
this.period = period;
}
public String call() {
try {
Thread.sleep(period);
} catch (InterruptedException ex) { }
return name;
}
}
maintenant, ci-dessous je vais vous montrer comment invokeAll
fonctionne:
final ExecutorService pool = Executors.newFixedThreadPool(2);
final List<? extends Callable<String>> callables = Arrays.asList(
new SleepingCallable("quick", 500),
new SleepingCallable("slow", 5000));
try {
for (final Future<String> future : pool.invokeAll(callables)) {
System.out.println(future.get());
}
} catch (ExecutionException | InterruptedException ex) { }
pool.shutdown();
produit le résultat suivant:
C:\dev\scrap>java CompletionExample
... after 5 s ...
quick
slow
en utilisant CompletionService
, nous voyons une sortie différente:
final ExecutorService pool = Executors.newFixedThreadPool(2);
final CompletionService<String> service = new ExecutorCompletionService<String>(pool);
final List<? extends Callable<String>> callables = Arrays.asList(
new SleepingCallable("slow", 5000),
new SleepingCallable("quick", 500));
for (final Callable<String> callable : callables) {
service.submit(callable);
}
pool.shutdown();
try {
while (!pool.isTerminated()) {
final Future<String> future = service.take();
System.out.println(future.get());
}
} catch (ExecutionException | InterruptedException ex) { }
produit le résultat suivant:
C:\dev\scrap>java CompletionExample
... after 500 ms ...
quick
... after 5 s ...
slow
notez que les heures sont relatives au démarrage du Programme, pas au message précédent.
vous pouvez trouver le code complet sur les deux ici .
en utilisant un ExecutorCompletionService
, vous pouvez être immédiatement informé lorsque chacun de vos travaux est terminé. En comparaison, ExecutorService.invokeAll(...)
attend pour tout de vos travaux à compléter avant de retourner la collecte de Future
s:
// this waits until _all_ of the jobs complete
List<Future<Object>> futures = threadPool.invokeAll(...);
au lieu de cela, lorsque vous utilisez un ExecutorCompletionService
, vous serez en mesure d'obtenir les travaux dès que chacun d'eux complète ce qui vous permet (par exemple) de les envoyer pour le traitement dans un autre pool de thread, log les résultats, etc..
ExecutorService threadPool = Executors.newFixedThreadPool(2);
ExecutorCompletionService<Result> compService
= new ExecutorCompletionService<Result>(threadPool);
for (MyJob job : jobs) {
compService.submit(job);
}
// shutdown the pool but the jobs submitted continue to run
threadPool.shutdown();
while (!threadPool.isTerminated()) {
// the take() blocks until any of the jobs complete
// this joins with the jobs in the order they _finish_
Future<Result> future = compService.take();
// this get() won't block
Result result = future.get();
// you can then put the result in some other thread pool or something
// to immediately start processing it
someOtherThreadPool.submit(new SomeNewJob(result));
}
Je n'ai jamais réellement utilisé Executorcompletservice, mais je pense que le cas où cela pourrait être plus utile que" normal " ExecutorService serait quand vous voulez recevoir les Futures des tâches terminées dans l'ordre d'achèvement. Avec invokeAll, vous obtenez juste une liste qui peut contenir un mélange de tâches incomplètes et achevées à tout moment.
la Comparaison en Considérant seulement l'Ordre des Résultats:
lorsque nous utilisons CompletionService
chaque fois qu'une tâche Soumise est terminée, le résultat sera poussé dans la file d'attente (ordre d'achèvement). Puis l'ordre des travaux soumis et les résultats retournés n'est plus le même. Donc, si vous êtes préoccupé par l'ordre quelles tâches ont été exécutées utiliser CompletionService
où comme invokeAll
renvoie une liste de Futures représentant les tâches, dans le même l'ordre établi par l'itérateur pour la liste des tâches, chacun de qui a terminé.