我刚在this blog post找到了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();
}
答案 0 :(得分:141)
省略许多细节:
答案 1 :(得分:89)
使用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");
}
}
}
}
答案 2 :(得分:10)
我认为javadoc最能回答CompletionService
何时有用ExecutorService
的问题。
将新异步任务的生成与已完成任务的结果消耗分离的服务。
基本上,这个界面允许程序让生产者创建和提交任务(甚至检查这些提交的结果),而不知道任何其他消费者对这些任务的结果。同时,知道CompletionService
的消费者可以poll
或take
结果而不知道生产者提交任务。
对于记录,我可能是错的,因为它已经很晚了,但我相当确定该博客文章中的示例代码会导致内存泄漏。如果没有活跃的消费者从ExecutorCompletionService
的内部队列中取出结果,我不确定该博主是如何预期排队的。
答案 3 :(得分:9)
如果您想并行执行多个任务,然后按照完成顺序使用它们,基本上您使用CompletionService
。所以,如果我执行5个工作,CompletionService
将给我第一个完成的工作。除了提交Executor
的能力之外,只有一个任务的示例不会赋予Callable
额外的价值。
答案 4 :(得分:4)
首先,如果我们不想浪费处理器时间,我们就不会使用
while (!future.isDone()) {
// Do some work...
}
我们必须使用
service.shutdown();
service.awaitTermination(14, TimeUnit.DAYS);
这段代码的坏处是它会关闭ExecutorService
。如果我们想继续使用它(即我们有一些recursicve任务创建),我们有两个选择: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 :(
}
}
答案 5 :(得分:1)
在运行时自己查看,尝试实现两个解决方案(Executorservice和Completionservice),您将看到它们的行为有多么不同,并且更清楚何时使用其中一个。 如果你想要http://rdafbn.blogspot.co.uk/2013/01/executorservice-vs-completionservice-vs.html
,这里有一个例子答案 6 :(得分:1)
假设您有5个长时间运行的任务(可调用任务),并且您已将这些任务提交给执行者服务。现在假设您不想等待所有5个任务竞争,而是想要对这些任务进行某种处理(如果任何一个完成)。现在可以通过在未来对象上编写轮询逻辑或使用此API来完成此操作。
答案 7 :(得分:0)
如果任务生成者对结果不感兴趣,并且处理执行程序服务执行的异步任务的结果是另一个组件的责任,那么您应该使用CompletionService。它可以帮助您将任务结果处理器与任务生成器分离。请参见示例http://www.zoftino.com/java-concurrency-executors-framework-tutorial
答案 8 :(得分: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通过将包装任务与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;
}
答案 9 :(得分: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 为pool-1-thread-9编译的任务结果应为9 结果是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-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 为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 为pool-1-thread-1编译的任务,结果应为1 未来的结果是1
================================================ ========
对于executorservice而言,只有在所有任务都经过编译之后,结果才能显示。
执行程序完成服务可返回的所有结果。