Si nous utilisons un ExecutorCompletionService nous pouvons soumettre une série de tâches en tant que Callable
s et obtenir le résultat en interaction avec le CompletionService
en tant que 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.
Pour autant que je sache, 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 ce sont la même idée avec une légère différence.
Alors pourquoi y a-t-il 2 façons différentes de soumettre une série de tâches? Ai-je raison de dire que les performances sont équivalentes? Y a-t-il un cas où l'un est plus approprié que l'autre? Je ne peux pas penser à un.
Utilisant un ExecutorCompletionService.poll/take
, vous recevez les Future
s à la fin, 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 tous soient terminés, 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 montrer comment fonctionne invokeAll
:
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();
Cela produit la sortie suivante:
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) { }
Cela produit la sortie suivante:
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 averti lorsque chacun de vos travaux est terminé. En comparaison, ExecutorService.invokeAll(...)
attend la fin de tous de vos travaux avant de renvoyer la collection 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 pourrez obtenir les travaux dès que chacun d'eux se termine, ce qui vous permet (par exemple) de les envoyer pour traitement dans un autre pool de threads, les résultats du journal, 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é ExecutorCompletionService, mais je pense que le cas où cela pourrait être plus utile que ExecutorService "normal" serait lorsque vous souhaitez recevoir les Futures des tâches terminées dans l'ordre d'achèvement. Avec invokeAll, vous obtenez simplement une liste qui peut contenir un mélange de tâches incomplètes et terminées à un moment donné.
Comparer en ne considérant que l'ordre des résultats:
Lorsque nous utilisons CompletionService
chaque fois qu'un travail soumis est terminé, le résultat est poussé dans la file d'attente (Ordre d'achèvement). Ensuite, l'ordre des travaux soumis et les résultats retournés ne sont plus les mêmes. Donc, si vous êtes préoccupé par l'ordre dans lequel les tâches ont été exécutées, utilisez CompletionService
Où As invokeAll
renvoie une liste de Futures représentant les tâches, dans le même ordre séquentiel que celui produit par l'itérateur pour la liste de tâches donnée, chacune étant terminée.