Whether to use invokeAll or submit - java Executor service
Asked Answered
E

4

31

I have a scenario where I have to execute 5 thread asynchronously for the same callable. As far as I understand, there are two options:

1) using submit(Callable)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = new ArrayList<>();
for(Callable callableItem: myCallableList){
    futures.add(executorService.submit(callableItem));
}

2) using invokeAll(Collections of Callable)

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));
  1. What should be the preferred way?
  2. Is there any disadvantage or performance impact in any of them compared to the other one?
Englut answered 23/12, 2015 at 17:10 Comment(0)
G
32

Depending on application requirement, either of them is preferred.

Option 1 : You are submitting the tasks to ExecutorService and you are not waiting for the completion of all tasks, which have been submitted to ExecutorService

Use case: If you don't want to wait after task submit() to ExecutorService, prefer Option 1.

Majorly it is used for Asynchronous request submission. For non-critical use cases, I will prefer the option

Example : Assume that you have booked a ticket ( say flight ticket). You want to log the request and response for housekeeping. The logging may be in a file OR database. In this use case, logging is not critical to send payment response to user. Submit the logging request to ExecutorService and that activity is handled asynchronously.

Option 2 : You are waiting for completion of all tasks, which have been submitted to ExecutorService.

Example: Assume that you have developed a multi player game. 10 players are part of the game. 5 players lost their money. Before start of the next game, you have given option for these 5 players to re-fill their balances. Now for starting next game, you have to wait for the result of all players re-fill requests. Then only you can start next game.

Is there any disadvantage or performance impact in any of them compared to the other one?

It depends on business use case as explained above. Both have their advantages / dis-advantages.

And one more important thing: Whatever option you prefer, FutureTask swallows Exceptions during task execution. You have to be careful. Have a look at this SE question: Handling Exceptions for ThreadPoolExecutor

With Java 8, you have one more option: ExecutorCompletionService

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 class is lightweight enough to be suitable for transient use when processing groups of tasks.

Have a look at related SE question: ExecutorCompletionService? Why do need one if we have invokeAll?

Gruver answered 14/1, 2016 at 19:57 Comment(5)
@downvoter, read question and anwser again to validate your judgement.Gruver
You said following for option 2: "You are waiting for completion of all tasks". What do you mean by "waiting"? Because the doc does not say anything about "waiting". Do you mean to say that if we submit() any more tasks to same ExecutorService after calling invokeAll(), those tasks will be delayed till the currently invoked list of tasks execute completely?Edmonton
Read the documentation of invokeAll:docs.oracle.com/javase/7/docs/api/java/util/concurrent/… => Executes the given tasks, returning a list of Futures holding their status and results when all completeGruver
Does "when all complete" mean the call to invokeAll() is blocking till all argument Callables complete their execution?Edmonton
Next statement after invokeAll() executes after all tasks complete their executionGruver
S
8

EDIT:

There is actually a difference between them. For some reason, invokeAll() will call get() for each future produced. Thus, it will wait the tasks to finish and that is why it may throw InterruptedException (while submit() throws nothing).

That's the Javadoc for the invokeAll() method:

Executes the given tasks, returning a list of Futures holding their status and results when all complete.

So, both strategies basically do the same, but if you call invokeAll() you'll be blocked until all tasks are done.


Original (incomplete) answer:

The invokeAll() method is there exactly for situations like these. You should definitely use it.

You don't really need to instantiate that List, though:

ExecutorService executorService = Executors.newFixedThreadPool(5);
List<Future<String>> futures = executorService.invokeAll(myCallableList));

This should be enough, and it looks way cleaner than the first alternative.

Soprano answered 23/12, 2015 at 17:30 Comment(2)
If invokeAll will block until all tasks are done, then isin't submit better. I can do processing of other tasks after submitting required tasks to ExecutorServiceEnglut
Yeah, if you do have other tasks to do in your main thread while your other threads are working, then using submit is better.Porphyroid
H
1

Suppose you have a task whose result depends on number of independentaly executable tasks. But for initial task to complete you only have limited time. Like its an API call.

So for example you have 100ms for top level task to complete and there are 10 dependant tasks as well. For that if you are using a submit here how the code will look like.

List<Callable> tasks = []// assume contains sub tasks
List<Future> futures = [] 
for(Callable task: tasks) {
   futures.add(service.submit(task));
}

for(Future futute: futures) {
    future.get(100, TimeUnit.MILLISECONDS);
}

So if each of sub tasks took exaclty 50ms to complete the above piece of code would take 50 ms. But if each of sub tasks took 1000 ms to complete the above would take 100 * 10 = 1000 ms or 1s. This is making difficult to compute the total time to be less than 100ms for all subtasks.

invokeAll method helps us in such scenario

List<Futures> futures = service.invokeall(tasks, 100, TimeUnit.MILLISECONDS)
for(Future future: futures) {
   if(!future.isCancelled()) {
       results.add(future.get());
   }
}

This way the maximum time it would take is inly 100 ms even if individual of subtasks took more than that.

Highlight answered 11/4, 2017 at 10:21 Comment(1)
if sub tasks took 99ms, 199ms, 299ms... future.get(100, TimeUnit.MILLISECONDS); still work, it's not we want.Lenlena
R
0

invokeAll() - blocking call, used when the next line of code depends on the task submitted.

submit() - non-blocking call, used when the next line of code doesn't depend on the task submitted.

Performance impact - there is no hard and fast differentiator. One method is better than the other, depending on your use. Sometimes, blocking calls is necessary to have the right outcome; in other cases, you can continue some other work before checking if the task is completed or not.

Ridinger answered 21/7, 2024 at 4:57 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.