我应何时在ExecutorService上使用CompletionService?


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

Answers:


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
有关另一个示例,请参见实践pg中的Java并发。130.当图像可用时,会使用CompletionService来渲染图像。
皮特2012年

可以安全假设take并且poll在CompletionService上是线程安全的吗?在您的示例中,首次调用时任务仍在执行take(),并且看不到任何显式同步。
拉菲安

1
take()确实是线程安全的。您可以阅读JavaDocs,但基本上take()会等待下一个完成的结果,然后将其返回。在CompletionService与工作BlockingQueue的输出。
凯文·希恩

3
有什么更好的方法来知道ExecutorCompletionService何时完成所有任务,而不是跟踪已提交任务的数量?
ryenus 2014年

1
@DebD,take()在没有更多结果时调用将导致该线程无限期等待。没有例外。您将必须设计逻辑以赶上这种情况并退出等待状态。这应该并不困难-通常,您将有一种方法可以知道所有任务都已完成而无需CompletionService告诉您该事实。
Bhaskar

159

省略许多细节:

  • ExecutorService =传入队列+工作线程
  • CompletionService =传入队列+工作线程+输出队列

12

基本上,CompletionService如果要并行执行多个任务,然后按其完成顺序使用它们,则使用a 。因此,如果我执行5个工作,CompletionService它将给我第一个完成的工作。只有一个任务的例子Executor除了提交任务的能力外没有任何额外的价值Callable


10

我认为javadoc最好地回答了何时CompletionService以某种方式有用的问题ExecutorService

一种服务,该服务将新异步任务的产生与完成任务的结果的消耗分开。

基本上,此接口允许程序让生产者创建并提交任务(甚至检查那些提交的结果),而无需知道这些任务的结果的任何其他使用者。同时,知道CompletionService可能polltake结果或结果的消费者却不知道生产者提交任务的消费者。

记录下来,我可能是错的,因为已经很晚了,但是我可以肯定的是,该博文中的示例代码会导致内存泄漏。如果没有积极的消费者将结果从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

假设您有5个长时间运行的任务(可调用任务),并且已将这些任务提交给执行者服务。现在,假设您不想等待所有5个任务竞争,而是想在任何一项完成时对这些任务进行某种处理。现在,可以通过在将来的对象上编写轮询逻辑或使用此API来完成此操作。



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

当您执行长期任务时,这将是性能的灾难。

使用completionservice,一旦任务完成,其结果将入队,您可以以较低的性能轮询队列。

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
您仅发布了实际代码的一部分,但即使在此处,注释“ brief spin-wait ”也表明该方法并非一直都在旋转。此外,在不知道完成服务使用的特定队列如何实现其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;
    }
}

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

执行程序完成服务的日志差异:当前线程是pool-1-thread-1,结果应为1当前线程是pool-1-thread-2,结果应为2当前线程是pool-1-thread-3,结果应为3,当前线程是pool-1-thread-4,结果应该是4,当前线程是pool-1-thread-6,结果应该是6,当前线程是pool-1-thread-5,结果应该是5,当前线程是pool-1-thread-7的结果应为7,当前线程为pool-1-thread-9,结果应为9,当前线程为pool-1-thread-8,其结果应为8。 1线程9的结果应该是9,结果是9池1的任务已编译的任务8线程,结果应该是8池1的任务已编译的任务,结果应该是7任务的压缩pool-1-thread-6的结果应为6的任务pool-1-thread-5的结果应该为5池1线程4的任务的结果应该为4池1线程3的任务应该是4的任务的结果

为pool-1-thread-2编译的任务,结果应为2

当前线程是pool-1-thread-1,结果应为1当前线程是pool-1-thread-3,结果应为3当前线程是pool-1-thread-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-thread-9结果应为9池1的任务已编译的线程8线程结果应为8池1线程的任务已编译的结果-thread-7结果应为7池1的任务完成的任务线程6的结果应该是6为pool-1-线程5编译的任务的结果应该是5为pool-1-thread-4压缩的任务的结果应该为4任务对于pool-1-thread-3压缩的任务的结果应该是3对于pool-1-thread-2的任务压缩的结果是2为pool-1-thread-1编译的任务的结果应为1将来的结果为1

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

对于executorservice来说,只有在所有任务都经过编译之后,结果才能显示出来。

执行程序完成服务可返回任何结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.