Je viens de trouver CompletionService dans cet article de blog . Cependant, cela ne présente pas vraiment les avantages de CompletionService par rapport à un ExecutorService standard. Le même code peut être écrit avec l'un ou l'autre. Alors, quand un CompletionService est-il utile?
Pouvez-vous donner un exemple de code court pour le rendre limpide? Par exemple, cet exemple de code montre simplement où un CompletionService n'est pas nécessaire (= équivalent à ExecutorService)
ExecutorService taskExecutor = Executors.newCachedThreadPool();
// CompletionService<Long> taskCompletionService =
// new ExecutorCompletionService<Long>(taskExecutor);
Callable<Long> callable = new Callable<Long>() {
@Override
public Long call() throws Exception {
return 1L;
}
};
Future<Long> future = // taskCompletionService.submit(callable);
taskExecutor.submit(callable);
while (!future.isDone()) {
// Do some work...
System.out.println("Working on something...");
}
try {
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Avec ExecutorService
, une fois que vous avez soumis les tâches à exécuter, vous devez coder manuellement pour obtenir efficacement les résultats des tâches terminées.
Avec CompletionService
, c'est à peu près automatisé. La différence n'est pas très évidente dans le code que vous avez présenté car vous ne soumettez qu'une tâche. Cependant, imaginez que vous ayez une liste de tâches à soumettre. Dans l'exemple ci-dessous, plusieurs tâches sont soumises à CompletionService. Ensuite, au lieu d'essayer de savoir quelle tâche est terminée (pour obtenir les résultats), il demande simplement à l'instance CompletionService de renvoyer les résultats dès qu'ils sont disponibles.
public class CompletionServiceTest {
class CalcResult {
long result ;
CalcResult(long l) {
result = l;
}
}
class CallableTask implements Callable<CalcResult> {
String taskName ;
long input1 ;
int input2 ;
CallableTask(String name , long v1 , int v2 ) {
taskName = name;
input1 = v1;
input2 = v2 ;
}
public CalcResult call() throws Exception {
System.out.println(" Task " + taskName + " Started -----");
for(int i=0;i<input2 ;i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(" Task " + taskName + " Interrupted !! ");
e.printStackTrace();
}
input1 += i;
}
System.out.println(" Task " + taskName + " Completed @@@@@@");
return new CalcResult(input1) ;
}
}
public void test(){
ExecutorService taskExecutor = Executors.newFixedThreadPool(3);
CompletionService<CalcResult> taskCompletionService = new ExecutorCompletionService<CalcResult>(taskExecutor);
int submittedTasks = 5;
for (int i=0;i< submittedTasks;i++) {
taskCompletionService.submit(new CallableTask (
String.valueOf(i),
(i * 10),
((i * 10) + 10 )
));
System.out.println("Task " + String.valueOf(i) + "subitted");
}
for (int tasksHandled=0;tasksHandled<submittedTasks;tasksHandled++) {
try {
System.out.println("trying to take from Completion service");
Future<CalcResult> result = taskCompletionService.take();
System.out.println("result for a task availble in queue.Trying to get()");
// above call blocks till atleast one task is completed and results availble for it
// but we dont have to worry which one
// process the result here by doing result.get()
CalcResult l = result.get();
System.out.println("Task " + String.valueOf(tasksHandled) + "Completed - results obtained : " + String.valueOf(l.result));
} catch (InterruptedException e) {
// Something went wrong with a task submitted
System.out.println("Error Interrupted exception");
e.printStackTrace();
} catch (ExecutionException e) {
// Something went wrong with the result
e.printStackTrace();
System.out.println("Error get() threw exception");
}
}
}
}
Omettre de nombreux détails:
Je pense que le javadoc répond le mieux à la question de savoir quand la CompletionService
est utile de la même manière qu'une ExecutorService
ne l’est pas.
Un service qui dissocie la production de nouvelles tâches asynchrones de la consommation des résultats des tâches terminées.
Fondamentalement, cette interface permet à un programme d’avoir des producteurs qui créent et soumettent des tâches (et même examinent les résultats de ces soumissions) sans connaître d’autres consommateurs des résultats de ces tâches. Pendant ce temps, les consommateurs qui sont au courant des résultats CompletionService
peuvent poll
pour ou take
sans se rendre compte que les producteurs ont soumis les tâches.
Pour mémoire, et je peux me tromper car il est assez tard, mais je suis à peu près certain que le code exemple de ce billet de blog provoque une fuite de mémoire. En l'absence d'un consommateur actif qui extrait les résultats de la file d'attente interne de la ExecutorCompletionService
, je ne sais pas comment le blogueur s'attendait à ce que cette file d'attente se vide.
Fondamentalement, vous utilisez CompletionService
si vous souhaitez exécuter plusieurs tâches en parallèle, puis les utiliser dans leur ordre d'exécution. Donc, si j’exécute 5 tâches, la CompletionService
me donnera le premier qui se termine. L'exemple où il n'y a qu'une seule tâche ne confère aucune valeur supplémentaire par rapport à Executor
en dehors de la possibilité de soumettre une Callable
.
Tout d’abord, si nous ne voulons pas perdre de temps de traitement, nous n’utiliserons pas
while (!future.isDone()) {
// Do some work...
}
Nous devons utiliser
service.shutdown();
service.awaitTermination(14, TimeUnit.DAYS);
La mauvaise chose à propos de ce code est qu'il va fermer ExecutorService
. Si nous voulons continuer à travailler avec elle (c'est-à-dire que nous avons une création de tâches récursive), nous avons deux alternatives: invokeAll ou ExecutorService
.
invokeAll
attendra jusqu'à ce que toutes les tâches soient terminées. ExecutorService
nous permet de prendre ou de consulter les résultats un par un.
Et, finement, exemple récursif:
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER);
ExecutorCompletionService<String> completionService = new ExecutorCompletionService<String>(executorService);
while (Tasks.size() > 0) {
for (final Task task : Tasks) {
completionService.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return DoTask(task);
}
});
}
try {
int taskNum = Tasks.size();
Tasks.clear();
for (int i = 0; i < taskNum; ++i) {
Result result = completionService.take().get();
if (result != null)
Tasks.add(result.toTask());
}
} catch (InterruptedException e) {
// error :(
} catch (ExecutionException e) {
// error :(
}
}
Observez-le vous-même au moment de l'exécution, essayez d'implémenter les deux solutions (Executorservice et Completionservice) et vous verrez en quoi elles se comportent différemment et il sera plus clair de savoir à quel moment utiliser l'une ou l'autre . si vous voulez http://rdafbn.blogspot.co.uk/2013/01/executorservice-vs-completionservice-vs.html
Supposons que vous ayez 5 tâches longues (tâches appelables) et que vous les ayez soumises au service executer. Maintenant, imaginez que vous ne vouliez pas attendre que les 5 tâches soient en concurrence, mais que vous souhaitiez effectuer un traitement sur ces tâches, le cas échéant. Cela peut maintenant être réalisé en écrivant une logique d'interrogation sur de futurs objets ou en utilisant cette API.
il existe un autre avantage à utiliser Complétionservice: Performance
lorsque vous appelez future.get()
, vous attendez:
à partir de Java.util.concurrent.CompletableFuture
private Object waitingGet(boolean interruptible) {
Signaller q = null;
boolean queued = false;
int spins = -1;
Object r;
while ((r = result) == null) {
if (spins < 0)
spins = (Runtime.getRuntime().availableProcessors() > 1) ?
1 << 8 : 0; // Use brief spin-wait on multiprocessors
else if (spins > 0) {
if (ThreadLocalRandom.nextSecondarySeed() >= 0)
--spins;
}
lorsque vous exécutez une tâche de longue durée, les performances sont désastreuses.
avec completeionservice, une fois la tâche terminée, le résultat est mis en file d'attente et vous pouvez interroger la file d'attente avec une performance réduite.
completeionservice y parvient en utilisant une tâche d'emballage avec un hook done
.
Java.util.concurrent.ExecutorCompletionService
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
Si le producteur de tâches n'est pas intéressé par les résultats et qu'il incombe à un autre composant de traiter les résultats des tâches asynchrones exécutées par le service de l'exécuteur, vous devez utiliser CompletionService. Il vous aide à séparer le processeur de résultats de tâches du producteur de tâches. Voir exemple http://www.zoftino.com/Java-concurrency-executors-framework-tutorial
package com.barcap.test.test00;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class ExecutorCompletest00 {
public static void main(String[] args) {
ExecutorService exc= Executors.newFixedThreadPool( 10 );
ExecutorCompletionService executorCompletionService= new ExecutorCompletionService( exc );
for (int i=1;i<10;i++){
Task00 task00= new Task00( i );
executorCompletionService.submit( task00 );
}
for (int i=1;i<20;i++){
try {
Future<Integer> future= (Future <Integer>) executorCompletionService.take();
Integer inttest=future.get();
System.out.println(" the result of completion service is "+inttest);
break;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
=============================================== =====
package com.barcap.test.test00;
import Java.util.*;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class ExecutorServ00 {
public static void main(String[] args) {
ExecutorService executorService=Executors.newFixedThreadPool( 9 );
List<Future> futList= new ArrayList <>( );
for (int i=1;i<10;i++) {
Future result= executorService.submit( new Task00( i ) );
futList.add( result );
}
for (Future<Integer> futureEach :futList ){
try {
Integer inm= futureEach.get();
System.out.println("the result of future executorservice is "+inm);
break;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
=============================================== =========
package com.barcap.test.test00;
import Java.util.concurrent.*;
/**
* Created by Sony on 25-04-2019.
*/
public class Task00 implements Callable<Integer> {
int i;
public Task00(int i) {
this.i = i;
}
@Override
public Integer call() throws Exception {
System.out.println(" the current thread is "+Thread.currentThread().getName() +" the result should be "+i);
int sleepforsec=100000/i;
Thread.sleep( sleepforsec );
System.out.println(" the task complted for "+Thread.currentThread().getName() +" the result should be "+i);
return i;
}
}
=============================================== =====================
différence de journaux pour le service d'achèvement de l'exécuteur: le thread actuel est pool-1-thread-1, le résultat doit être 1 le thread actuel est pool-1-thread-2 le résultat doit être 2 le thread actuel est pool-1-thread- 3 le résultat doit être 3 le thread actuel est pool-1-thread-4 le résultat doit être 4 le thread actuel est pool-1-thread-6 le résultat doit être 6 le thread actuel est pool-1-thread-5 le le résultat doit être 5 le thread actuel est pool-1-thread-7 le résultat doit être 7 le thread actuel est pool-1-thread-9 le résultat doit être 9 le thread actuel est pool-1-thread-8 le résultat doit 8 la tâche accomplie pour pool-1-thread-9 le résultat doit être 9 le résultat est 9 la tâche accomplie pour pool-1-thread-8 le résultat doit être 8 la tâche complétée pour pool-1-thread-7 le résultat doit être 7 la tâche accomplie pour pool-1-thread-6 le résultat doit être 6 la tâche complétée pour pool-1-thread-5 le résultat doit être 5 la tâche complétée pour pool-1-thread-4 le résultat doit être 4 la tâche accomplie pour pool-1-thread-3, le résultat devrait être 3
le thread actuel est pool-1-thread-1 le résultat doit être 1 le thread actuel est pool-1-thread-3 le résultat doit être 3 le thread actuel est pool-1-thread-2 le résultat doit être 2 le courant le thread est pool-1-thread-5 le résultat doit être 5 le thread actuel est pool-1-thread-4 le résultat doit être 4 le thread actuel est pool-1-thread-6 le résultat doit être 6 le thread actuel est pool-1-thread-7 le résultat doit être 7 le thread actuel est pool-1-thread-8 le résultat doit être 8 le thread actuel est pool-1-thread-9 le résultat doit être 9 la tâche remplie pour pool- 1-thread-9 le résultat devrait être 9 la tâche terminée pour pool-1-thread-8 le résultat devrait être 8 la tâche terminée pour pool-1-thread-7 le résultat devrait être 7 la tâche terminée pour pool-1- thread-6 le résultat doit être 6 la tâche terminée pour pool-1-thread-5 le résultat doit être 5 la tâche terminée pour pool-1-thread-4 le résultat doit être 4 la tâche terminée pour pool-1-thread- 3 le résultat devrait être 3 la tâche accomplie pour po ol-1-thread-2 le résultat devrait être 2 la tâche accomplie pour pool-1-thread-1 le résultat devrait être 1 le résultat du futur est 1
=============================================== =====
pour l'exécutant, le résultat ne sera disponible qu'une fois toutes les tâches accomplies.
executor completeionservice tout résultat disponible effectue ce retour.