If we use an ExecutorCompletionService we can submit a series of tasks as Callable
s and get the result interacting with the CompletionService
as a queue
.
But there is also the invokeAll
of ExecutorService
that accepts a Collection
of tasks and we get a list of Future
to retrieve the results.
As far as I can tell, there is no benefit in using one or over the other (except that we avoid a for
loop using an invokeAll
that we would have to submit
the tasks to the CompletionService
) and essentially they are the same idea with a slight difference.
So why are there 2 different ways to submit a series of tasks? Am I correct that performance wise they are equivalent? Is there a case that one is more suitable than the other? I can't think of one.
It looks as invokeAll is blocking.
A CompletionService that uses a supplied Executor to execute tasks. This class arranges that submitted tasks are, upon completion, placed on a queue accessible using take .
The practical main difference between ExecutorService and CompletionService is: ExecutorService get() will try to retrieve the results in the submitted order waiting for completion. CompletionService take() + get() will try to retrieve the results in the completion order disregarding the submission order.
Using a ExecutorCompletionService.poll/take
, you are receiving the Future
s as they finish, in completion order (more or less). Using ExecutorService.invokeAll
, you do not have this power; you either block until are all completed, or you specify a timeout after which the incomplete are cancelled.
static class SleepingCallable implements Callable<String> { final String name; final long period; SleepingCallable(final String name, final long period) { this.name = name; this.period = period; } public String call() { try { Thread.sleep(period); } catch (InterruptedException ex) { } return name; } }
Now, below I will demonstrate how invokeAll
works:
final ExecutorService pool = Executors.newFixedThreadPool(2); final List<? extends Callable<String>> callables = Arrays.asList( new SleepingCallable("quick", 500), new SleepingCallable("slow", 5000)); try { for (final Future<String> future : pool.invokeAll(callables)) { System.out.println(future.get()); } } catch (ExecutionException | InterruptedException ex) { } pool.shutdown();
This produces the following output:
C:\dev\scrap>java CompletionExample ... after 5 s ... quick slow
Using CompletionService
, we see a different output:
final ExecutorService pool = Executors.newFixedThreadPool(2); final CompletionService<String> service = new ExecutorCompletionService<String>(pool); final List<? extends Callable<String>> callables = Arrays.asList( new SleepingCallable("slow", 5000), new SleepingCallable("quick", 500)); for (final Callable<String> callable : callables) { service.submit(callable); } pool.shutdown(); try { while (!pool.isTerminated()) { final Future<String> future = service.take(); System.out.println(future.get()); } } catch (ExecutionException | InterruptedException ex) { }
This produces the following output:
C:\dev\scrap>java CompletionExample ... after 500 ms ... quick ... after 5 s ... slow
Note the times are relative to program start, not the previous message.
You can find full code on both here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With