Коли слід використовувати CompletionService над ExecutorService?


78

Я щойно знайшов CompletionService у цьому дописі в блозі . Однак це насправді не демонструє переваг CompletionService перед стандартним ExecutorService. Один і той самий код можна написати будь-яким. Отже, коли CompletionService корисний?

Чи можете ви дати короткий зразок коду, щоб він став кристально чистим? Наприклад, цей зразок коду просто показує, де CompletionService не потрібен (= еквівалент 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();
    }

Відповіді:


99

Після того ExecutorService, як ви надіслали завдання для запуску, вам потрібно вручну кодувати для ефективного отримання результатів виконаних завдань.

З CompletionService, це майже автоматизовано. Різниця не дуже помітна в коді, який ви представили, оскільки ви подаєте лише одне завдання. Однак уявіть, що у вас є список завдань, які потрібно подати. У прикладі нижче декілька завдань подаються до CompletionService. Потім, замість того, щоб намагатись з’ясувати, яке завдання виконано (щоб отримати результати), він просто просить екземпляр CompletionService повернути результати, коли вони стануть доступними.

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
для іншого прикладу див. Java Concurrency in Practice pg. 130. Там CompletionService використовується для візуалізації зображень, коли вони стають доступними.
Піт,

Безпечно припустити, takeа pollна CompletionService безпечні потоки? У вашому прикладі завдання все ще виконуються, коли ви вперше викликаєте take(), і я не бачу явної синхронізації.
raffian

1
take()справді є безпечним для потоків. Ви можете читати в JavaDocs, але в основному take()чекатимете наступного завершеного результату, а потім повертаєте це. CompletionServiceПрацює з BlockingQueueдля виходу.
Кевін Шихан,

3
чи є кращий спосіб дізнатись, коли ExecutorCompletionService закінчив усі завдання, а не відстежувати кількість надісланих завдань?
ryenus

1
@DebD, дзвінок, take()коли результатів більше не буде, призведе до того, що ця нитка буде чекати необмежено довго. Ніяких винятків. Вам доведеться розробити свою логіку, щоб вловити цю ситуацію і вийти з режиму очікування. Це не повинно бути складно - ви, як правило, знаєте, що всі ваші завдання виконуються без того, щоб CompletionService повідомив вам цей факт.
Bhaskar

159

Опускаючи безліч деталей:

  • ExecutorService = вхідна черга + робочі потоки
  • CompletionService = вхідна черга + робочі потоки + черга виводу

12

В основному ви використовуєте a, CompletionServiceякщо хочете виконувати кілька завдань паралельно, а потім працювати з ними в порядку їх завершення. Отже, якщо я CompletionServiceвиконаю 5 завдань, то програма дасть мені перше, що закінчить. Приклад, коли існує лише одне завдання, не надає додаткової цінності, Executorкрім можливості подати файл Callable.


10

Я думаю, що javadoc найкраще відповідає на питання, коли CompletionServiceкорисно певним чином не ExecutorServiceє.

Послуга, яка відокремлює виробництво нових асинхронних завдань від споживання результатів виконаних завдань.

По суті, цей інтерфейс дозволяє програмі мати виробників, які створюють і подають завдання (і навіть вивчають результати цих подань), не знаючи про інших споживачів результатів цих завдань. Тим часом споживачі, які знають про те, що CompletionServiceможе pollдля абоtake результатів, не знаючи, що виробники подають завдання.

Для запису, і я можу помилятися, оскільки це досить пізно, але я впевнений, що зразок коду в цій публікації в блозі викликає витік пам'яті. Без активного споживача, який виводить результати з ExecutorCompletionServiceвнутрішньої черги, я не впевнений, як блогер очікував, що ця черга вичерпається.


4

Перш за все, якщо ми не хочемо витрачати час процесора, ми не будемо використовувати

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

Ми повинні використовувати

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

Погано в цьому коді те, що він вимкнеться ExecutorService. Якщо ми хочемо продовжити роботу з ним (тобто у нас є рекурсивне створення завдання), у нас є дві альтернативи: invokeAll або ExecutorService.

invokeAll буде чекати, поки всі завдання не будуть виконані. ExecutorServiceнадає нам можливість по черзі брати або опитувати результати.

І, нарешті, рекурсивний приклад:

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

Перевірте це самі під час виконання, спробуйте впровадити обидва рішення (Executorservice та Completionservice), і ви побачите, наскільки вони поводяться по-різному, і буде зрозуміліше, коли використовувати те чи інше. Тут є приклад, якщо ви хочете http://rdafbn.blogspot.co.uk/2013/01/executorservice-vs-completionservice-vs.html


1

Скажімо, у вас є 5 запущених завдань (викликається завдання), і ви надіслали це завдання службі виконавця. Тепер уявіть, що ви не хочете чекати, поки всі 5 завдань будуть конкурувати, натомість ви хочете виконати якусь обробку цього завдання, якщо якесь із них виконає. Тепер це можна зробити, записавши логіку опитування на майбутні об’єкти, або скориставшись цим API.


0

Якщо виробник завдання не зацікавлений у результатах, а обробка результатів асинхронного завдання, що виконується службою виконавця, несе відповідальність іншого компонента, тоді слід використовувати CompletionService. Це допоможе вам відокремити процесор результатів завдань від виробника завдань. Див. Приклад http://www.zoftino.com/java-concurrency-executors-framework-tutorial


0

є ще одна перевага використання служби завершення: продуктивність

коли дзвоните future.get() , вас чекає спін:

від 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;
            }

коли у вас є тривале завдання, це буде катастрофою для продуктивності.

при завершенні служби, як тільки завдання буде виконано, його результат буде розміщено в черзі, і ви зможете опитувати чергу з меншою продуктивністю.

completeservice досягають цього, використовуючи завдання обгортання 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;
}

1
Ви опублікували лише фрагмент власне коду, але навіть там коментар “ коротке віджимання ” вказує на те, що цей метод не розкручує весь час. Далі, не знаючи, як конкретна черга, яка використовується службою добудови, реалізувала свій pollметод, немає підстав стверджувати, що вона мала "нижчі витрати на продуктивність".
Холгер

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

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

різниця журналів для служби виконання виконавця:поточний потік - пул-1-потік-1 результат повинен бути 1 поточний потік пул-1-потік-2 результат повинен бути 2 поточний потік є пул-1-потік-3 результат повинен бути 3 поточний потік пул-1-потік-4 результат повинен бути 4 поточний потік пул-1-потік-6 результат повинен бути 6 поточний потік пул-1-потік-5 результат повинен бути 5 поточний потік pool-1-thread-7 результат повинен бути 7 поточний потік pool-1-thread-9 результат повинен бути 9 поточний потік pool-1-thread-8 результат повинен бути 8 завдання виконане для pool- 1-thread-9 результат повинен бути 9 the result is 9 завдання виконано для pool-1-thread-8 результат повинен бути 8 завдання виконано для pool-1-thread-7 результат повинен бути 7 завдання виконано для pool-1-thread-6 результатом має бути 6 завдання, для якого виконано завданняpool-1-thread-5 результат повинен бути 5 завдання виконано для pool-1-thread-4 результат повинен бути 4 завдання виконано для pool-1-thread-3 результат повинен бути 3

завдання, виконане для pool-1-thread-2, результат повинен бути 2

поточний потік - пул-1-потік-1 результат повинен бути 1 поточний потік є пул-1-потік-3 результат повинен бути 3 поточний потік є пул-1-потік-2 результат повинен бути 2 поточний потік pool-1-thread-5 результат повинен бути 5 поточний потік pool-1-thread-4 результат повинен бути 4 поточний потік pool-1-thread-6 результат повинен бути 6 поточний потік pool-1-thread-7 результат повинен бути 7 поточний потік pool-1-thread-8 результат повинен бути 8 поточний потік pool-1-thread-9 результат повинен бути 9 завдання виконане для pool- 1-потік-9 результат повинен бути 9 завдання виконано для пулу-1-потік-8 результат має бути 8 завдання виконано для пулу-1-потік-7 результат має бути 7 завдання виконано для пул-1 потік-6 результат повинен бути 6 завдання, виконане для пулу-1-потік-5 результатмає бути 5 завдання, виконане для pool-1-thread-4 результат має бути 4 завдання, виконане для pool-1-thread-3 результат повинен бути 3 завдання, виконане для pool-1-thread-2 результат повинен бути 2 завдання, виконане для pool-1-thread-1, результат повинен бути 1, результат майбутнього - 1

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

для виконавчої служби результат буде доступним лише після виконання всіх завдань.

виконавець завершенняобслуговування будь-який доступний результат зробити це повернення.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.