Quand dois-je utiliser un CompletionService sur un ExecutorService?

Je viens de trouver CompletionService dans cet article de blog . Cependant, cela ne montre pas vraiment les avantages de CompletionService sur un ExecutorService standard. Le même code peut être écrit avec soit. Alors, quand un service CompletionService est-il utile?

Pouvez-vous donner un petit exemple de code pour le rendre clair? Par exemple, cet exemple de code montre simplement où un CompletionService n’est pas nécessaire (= équivalent à ExecutorService)

ExecutorService taskExecutor = Executors.newCachedThreadPool(); // CompletionService taskCompletionService = // new ExecutorCompletionService(taskExecutor); Callable callable = new Callable() { @Override public Long call() throws Exception { return 1L; } }; Future 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 plutôt automatisé. La différence n’est pas très évidente dans le code que vous avez présenté, car vous ne soumettez qu’une seule tâche. Cependant, imaginez que vous avez 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 s’est terminée (pour obtenir les résultats), elle 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 { Ssortingng taskName ; long input1 ; int input2 ; CallableTask(Ssortingng 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 taskCompletionService = new ExecutorCompletionService(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 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 " + Ssortingng.valueOf(tasksHandled) + "Completed - results obtained : " + Ssortingng.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 beaucoup de détails:

  • ExecutorService = queue entrante + threads de travail
  • CompletionService = queue entrante + threads de travail + queue de sortie

Je pense que le javadoc répond le mieux à la question de savoir si CompletionService est utile d’une manière qui n’existe pas avec ExecutorService .

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 du CompletionService pourraient rechercher ou take résultats sans être au courant des producteurs soumettant les tâches.

Pour mémoire, et je peux me tromper car il est un peu tard, mais je suis à peu près certain que l’exemple de code dans cet article provoque une fuite de mémoire. Sans un consommateur actif prenant les résultats de la queue interne de ExecutorCompletionService , je ne suis pas sûr de la façon dont le blogueur s’attend à ce que cette queue se vide.

Fondamentalement, vous utilisez un CompletionService si vous souhaitez exécuter plusieurs tâches en parallèle, puis les utiliser dans leur ordre d’achèvement. Donc, si j’exécute 5 travaux, le 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 à un Executor exception de la possibilité de soumettre un Callable .

Tout d’abord, si nous ne voulons pas gaspiller le temps processeur, 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 cela (c’est-à-dire que nous avons créé des tâches récursives), nous avons deux alternatives: invokeAll ou ExecutorService .

invokeAll attendra que toutes les tâches soient terminées. ExecutorService nous donne la possibilité de prendre ou d’interroger les résultats un par un.

Et, finement, exemple récursif:

 ExecutorService executorService = Executors.newFixedThreadPool(THREAD_NUMBER); ExecutorCompletionService completionService = new ExecutorCompletionService(executorService); while (Tasks.size() > 0) { for (final Task task : Tasks) { completionService.submit(new Callable() { @Override public Ssortingng 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 :( } } 

Voyez-le vous-même au moment de l’exécution, essayez d’implémenter les deux solutions (Executorservice et Completionservice) et vous verrez à quel point elles se comportent et il sera plus clair d’utiliser l’une ou l’autre. Il y a un exemple ici si vous voulez http://rdafbn.blogspot.co.uk/2013/01/executorservice-vs-completionservice-vs.html

Supposons que vous ayez 5 longues tâches à exécuter (tâche appelable) et que vous avez soumis ces tâches au service d’exécution. Maintenant, imaginez que vous ne vouliez pas attendre que toutes les tâches 5 soient en concurrence, mais que vous vouliez effectuer un traitement quelconque sur ces tâches si l’une d’entre elles était terminée. Maintenant, cela peut être fait en écrivant la logique d’interrogation sur les objects futurs ou en utilisant cette API.

Si le générateur de tâches n’est pas intéressé par les résultats et qu’il est de la responsabilité d’un autre composant de traiter les résultats d’une tâche asynchrone exécutée par le service exécuteur, vous devez alors utiliser CompletionService. Il vous aide à séparer le processeur de résultat de tâche du producteur de tâche. Voir l’exemple http://www.zoftino.com/java-concurrency-executors-framework-tutorial