When should I use a CompletionService over an ExecutorService?
I just found CompletionService in this blog post. However, this does't really showcases the advantages of CompletionService over a standard ExecutorService. The same code can be written with either. So, when is a CompletionService useful?
Can you give a short code sample to make it crystal clear? For example, this code sample just shows where a CompletionService is not needed (=equivalent to 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();
}
Omitting many details:
- ExecutorService = incoming queue + worker threads
- CompletionService = incoming queue + worker threads + output queue
With ExecutorService
, once you have submitted the tasks to run, you need to manually code for efficiently getting the results of the tasks completed.
With CompletionService
, this is pretty much automated. The difference is not very evident in the code you have presented because you are submitting just one task. However, imagine you have a list of tasks to be submitted. In the example below, multiple tasks are submitted to the CompletionService. Then, instead of trying to find out which task has completed (to get the results), it just asks the CompletionService instance to return the results as they become available.
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");
}
}
}
}
Basically you use a CompletionService
if you want to execute multiple tasks in parallel and then work with them in their completion order. So, if I execute 5 jobs, the CompletionService
will give me the first one that that finishes. The example where there is only a single task confers no extra value over an Executor
apart from the ability to submit a Callable
.