Wann sollte ich einen CompletionService über einen ExecutorService verwenden?


78

Ich habe gerade CompletionService in diesem Blog-Beitrag gefunden . Dies zeigt jedoch nicht wirklich die Vorteile von CompletionService gegenüber einem Standard-ExecutorService. Der gleiche Code kann mit beiden geschrieben werden. Wann ist ein CompletionService nützlich?

Können Sie ein kurzes Codebeispiel geben, um es kristallklar zu machen? Dieses Codebeispiel zeigt beispielsweise nur, wo ein CompletionService nicht benötigt wird (= entspricht 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();
    }

Antworten:


99

Mit ExecutorServicewenn Sie die Aufgaben, Erklärungen abgegeben haben zu laufen, müssen Sie effizient manuell Code für die Ergebnisse der Aufgaben immer abgeschlossen.

Mit CompletionServiceist dies ziemlich automatisiert. Der Unterschied ist in dem von Ihnen präsentierten Code nicht sehr offensichtlich, da Sie nur eine Aufgabe einreichen. Stellen Sie sich jedoch vor, Sie haben eine Liste der Aufgaben, die eingereicht werden müssen. Im folgenden Beispiel werden mehrere Aufgaben an den CompletionService gesendet. Anstatt herauszufinden, welche Aufgabe abgeschlossen wurde (um die Ergebnisse zu erhalten), wird die CompletionService-Instanz lediglich aufgefordert, die Ergebnisse zurückzugeben, sobald sie verfügbar sind.

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");
                }
            }
        }
    }

7
Ein weiteres Beispiel finden Sie unter Java-Parallelität in der Praxis. 130. Dort wird ein CompletionService verwendet, um Bilder zu rendern, sobald sie verfügbar sind.
Pete

Sicher anzunehmen takeund pollbei CompletionService sind threadsicher? In Ihrem Beispiel werden Aufgaben beim ersten Aufruf noch ausgeführt take(), und ich sehe keine explizite Synchronisierung.
Raffian

1
take()ist in der Tat threadsicher. Sie können die JavaDocs einlesen, take()warten jedoch grundsätzlich auf das nächste abgeschlossene Ergebnis und geben es dann zurück. Das CompletionServicefunktioniert mit einem BlockingQueuefür die Ausgabe.
Kevin Sheehan

3
Gibt es eine bessere Möglichkeit, festzustellen, wann ExecutorCompletionService alle Aufgaben abgeschlossen hat, als die Anzahl der übermittelten Aufgaben zu verfolgen?
Ryenus

1
@DebD Wenn Sie aufrufen, take()wenn keine Ergebnisse mehr vorliegen, wartet dieser Thread auf unbestimmte Zeit. Keine Ausnahmen. Sie müssen Ihre Logik entwerfen, um diese Situation zu erfassen und das Warten zu beenden. Dies sollte nicht schwierig sein - normalerweise können Sie feststellen, dass alle Ihre Aufgaben erledigt sind, ohne dass der CompletionService Ihnen dies mitteilt.
Bhaskar

159

Viele Details weglassen:

  • ExecutorService = eingehende Warteschlange + Arbeitsthreads
  • CompletionService = eingehende Warteschlange + Worker-Threads + Ausgabewarteschlange

12

Grundsätzlich verwenden Sie a, CompletionServicewenn Sie mehrere Aufgaben parallel ausführen und dann in ihrer Abschlussreihenfolge mit ihnen arbeiten möchten. Wenn ich also 5 Jobs ausführe, CompletionServicegibt mir der den ersten, der beendet ist. Das Beispiel, in dem es nur eine einzige Aufgabe gibt, verleiht Executoraußer der Möglichkeit, eine Aufgabe einzureichen, keinen zusätzlichen Wert Callable.


10

Ich denke, der Javadoc beantwortet am besten die Frage, wann das CompletionServicein einer Weise nützlich ExecutorServiceist und nicht.

Ein Dienst, der die Erstellung neuer asynchroner Aufgaben vom Verbrauch der Ergebnisse abgeschlossener Aufgaben entkoppelt.

Grundsätzlich ermöglicht diese Schnittstelle einem Programm, Produzenten zu haben, die Aufgaben erstellen und einreichen (und sogar die Ergebnisse dieser Einreichungen untersuchen), ohne über andere Verbraucher der Ergebnisse dieser Aufgaben Bescheid zu wissen. In der Zwischenzeit Verbraucher, die sich der CompletionServiceMöglichkeit polloder der takeErgebnisse bewusst sind, ohne zu wissen, dass die Hersteller die Aufgaben einreichen.

Für die Aufzeichnung, und ich könnte mich irren, weil es ziemlich spät ist, aber ich bin ziemlich sicher, dass der Beispielcode in diesem Blog-Beitrag einen Speicherverlust verursacht. Ohne einen aktiven Verbraucher, der Ergebnisse aus der ExecutorCompletionServiceinternen Warteschlange des Benutzers entnimmt, bin ich mir nicht sicher, wie der Blogger damit gerechnet hat, dass diese Warteschlange leer wird.


4

Erstens, wenn wir keine Prozessorzeit verschwenden wollen, werden wir nicht verwenden

while (!future.isDone()) {
        // Do some work...
}

Wir müssen verwenden

service.shutdown();
service.awaitTermination(14, TimeUnit.DAYS);

Das Schlechte an diesem Code ist, dass er heruntergefahren wird ExecutorService. Wenn wir damit weiterarbeiten möchten (dh wir haben eine rekursive Aufgabenerstellung), haben wir zwei Alternativen: invokeAll oder ExecutorService.

invokeAllwartet, bis alle Aufgaben abgeschlossen sind. ExecutorServicegewährt uns die Möglichkeit, Ergebnisse einzeln zu erfassen oder abzufragen.

Und schließlich ein rekursives Beispiel:

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 :(
    }
}


1

Angenommen, Sie haben 5 Aufgaben mit langer Laufzeit (aufrufbare Aufgabe) und haben diese Aufgabe an den Executer Service gesendet. Stellen Sie sich nun vor, Sie möchten nicht darauf warten, dass alle 5 Aufgaben miteinander konkurrieren, sondern eine Art Verarbeitung für diese Aufgabe durchführen, wenn eine abgeschlossen ist. Dies kann jetzt entweder durch Schreiben einer Abfragelogik für zukünftige Objekte oder durch Verwenden dieser API erfolgen.


0

Wenn der Taskproduzent nicht an den Ergebnissen interessiert ist und es in der Verantwortung einer anderen Komponente liegt, die Ergebnisse der vom Executor-Service ausgeführten asynchronen Task zu verarbeiten, sollten Sie CompletionService verwenden. Es hilft Ihnen bei der Trennung des Task-Ergebnisprozessors vom Task-Produzenten. Siehe Beispiel http://www.zoftino.com/java-concurrency-executors-framework-tutorial


0

Die Verwendung von Completionservice bietet noch einen weiteren Vorteil: Leistung

Wenn Sie anrufen future.get(), warten Sie:

von 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;
            }

Wenn Sie eine lange laufende Aufgabe haben, ist dies eine Leistungskatastrophe.

Wenn der Abschlussdienst abgeschlossen ist, wird das Ergebnis in die Warteschlange gestellt, und Sie können die Warteschlange mit geringerer Leistung überhand abfragen.

Vervollständigungsservice erreichen dies durch die Verwendung einer Wrap-Task mit einem doneHook.

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;
}

1
Sie haben nur ein Fragment des eigentlichen Codes gepostet, aber selbst dort zeigt der Kommentar „ kurzes Schleudern “ an, dass diese Methode nicht die ganze Zeit wartet. Ohne zu wissen, wie die vom Abschlussdienst verwendete bestimmte Warteschlange ihre pollMethode implementiert hat , gibt es keinen Grund zu der Annahme, dass sie einen „geringeren Leistungsaufwand“ hatte.
Holger

0
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;
    }
}

================================================== ====================

Unterschied der Protokolle für den Executor Completion Service:Der aktuelle Thread ist Pool-1-Thread-1. Das Ergebnis sollte 1 sein. Der aktuelle Thread ist Pool-1-Thread-2. Das Ergebnis sollte 2 sein. Der aktuelle Thread ist Pool-1-Thread-3. Das Ergebnis sollte 3 sein Thread ist Pool-1-Thread-4 Das Ergebnis sollte 4 sein. Der aktuelle Thread ist Pool-1-Thread-6. Das Ergebnis sollte 6 sein. Der aktuelle Thread ist Pool-1-Thread-5. Das Ergebnis sollte 5 sein. Der aktuelle Thread ist Pool-1-Thread-7 Das Ergebnis sollte 7 sein. Der aktuelle Thread ist Pool-1-Thread-9. Das Ergebnis sollte 9 sein. Der aktuelle Thread ist Pool-1-Thread-8. Das Ergebnis sollte 8 sein. Die Aufgabe wurde für Pool erledigt. 1-Thread-9 Das Ergebnis sollte 9 sein. Das Ergebnis ist 9 Die Aufgabe wurde für Pool-1-Thread-8 erledigt. Das Ergebnis sollte 8 sein. Die Aufgabe wurde für Pool-1-Thread-7 erledigt. Das Ergebnis sollte 7 sein. Die Aufgabe wurde erledigt Pool-1-Thread-6 Das Ergebnis sollte 6 die Aufgabe sein, für die erledigt wurdePool-1-Thread-5 Das Ergebnis sollte 5 sein. Die Aufgabe wurde für Pool-1-Thread-4 erledigt. Das Ergebnis sollte 4 sein. Die Aufgabe wurde für Pool-1-Thread-3 erledigt. Das Ergebnis sollte 3 sein

Die für Pool-1-Thread-2 abgeschlossene Aufgabe sollte 2 sein

Der aktuelle Thread ist Pool-1-Thread-1. Das Ergebnis sollte 1 sein. Der aktuelle Thread ist Pool-1-Thread-3. Das Ergebnis sollte 3 sein. Der aktuelle Thread ist Pool-1-Thread-2. Das Ergebnis sollte 2 der aktuelle sein Thread ist Pool-1-Thread-5 Das Ergebnis sollte 5 sein. Der aktuelle Thread ist Pool-1-Thread-4. Das Ergebnis sollte 4 sein. Der aktuelle Thread ist Pool-1-Thread-6. Das Ergebnis sollte 6 sein. Der aktuelle Thread ist Pool-1-Thread-7 Das Ergebnis sollte 7 sein. Der aktuelle Thread ist Pool-1-Thread-8. Das Ergebnis sollte 8 sein. Der aktuelle Thread ist Pool-1-Thread-9. Das Ergebnis sollte 9 sein. Die Aufgabe wurde für Pool erledigt. 1-Thread-9 Das Ergebnis sollte 9 sein. Die Aufgabe wurde für Pool-1-Thread-8 erledigt. Das Ergebnis sollte 8 sein. Die Aufgabe wurde für Pool-1-Thread-7 erledigt. Das Ergebnis sollte 7 sein. Die Aufgabe wurde für Pool-1 erledigt. Thread-6 das Ergebnis sollte 6 die Aufgabe sein, die für Pool-1-Thread-5 das Ergebnis erledigt wurdesollte 5 die Aufgabe sein, die für Pool-1-Thread-4 erledigt wurde, das Ergebnis sollte 4 die Aufgabe sein, die für Pool-1-Thread-3 erledigt wurde, das Ergebnis sollte 3 sein, die Aufgabe sollte für Pool-1-Thread-2 erledigt sein, das Ergebnis sollte sein 2 Die Aufgabe für Pool-1-Thread-1 abgeschlossen. Das Ergebnis sollte 1 sein. Das Ergebnis der Zukunft ist 1

================================================== =====

Für Executorservice ist das Ergebnis erst verfügbar, nachdem alle Aufgaben erledigt wurden.

Executor Completeservice Jedes verfügbare Ergebnis macht diese Rückgabe.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.