Is the execute method in ExecutorService of Java Concurrency underrated ?

Chirag M. Onkarappa
4 min readDec 4, 2018

--

The multithreading was simplified through the Java 5 release with the introduction of the ExecutorService. When we look at the two methods it provides to complete a callable (tasks that returns value) - execute and submit, the immediate difference pointed out is that the first has no return type but the latter returns a Java Future by default. Now, due to the powerful options Java Future provides, its tempting and almost standardized to use submit. In this fairy land of submit, have we forgotten what execute can even do?.

This article is an effort not to differentiate or prove that execute is better than submit but tries to point out the relevance of the underrated. Lets start by having a demo wherein we consider two programs, one using execute and the other using submit. In each of the programs, there is a list containing Randomly generated numbers. The multi-threaded tasks in each of the program should the ExecutorService chooses to accept is: Find whether the numbers are prime number or not — return String. To get maximum CPU time per task, the algorithm implemented is as slow as possible and the numbers are a nine digit.

The below example is the conventional way to use submit for a callable (returning a object). Each tasks by default returns a Java-Future which makes the developer job a lot easier. The IsPrime class implements callable and returns a string. The implementation is non-blocking Java-Future through the use of isDone from the interface methods.

List<Integer> listRandomNum =new ArrayList<Integer>();
List<Future<String>> taskFutures =new ArrayList<Future<String>>();
ExecutorService service =Executors.newFixedThreadPool(10);

/**
* Adding randomly generated 9-digit number to the list
*/
for(int i=0; i<4; i++) {
Random rand = new Random();
int number = rand.nextInt(999999999) + 100000000;
listRandomNum.add(number);
}
/**
* Use submit() for each of the tasks and add the futures to list
*/
for(Integer num:listRandomNum) {
Future<String> task = service.submit(new IsPrime(num));
taskFutures.add(task);
}

/**
* Use isDone() of Future to check for the task completeness and
* loop until all the tasks from the list is completed
*/
while(taskFutures.size() != 0) {
for(int i=0; i<taskFutures.size(); i++) {
if(taskFutures.get(i).isDone()) {
String ans = "";
try{
ans = taskFutures.get(i).get();
}
catch(Exception e){
System.err.println(e);
}
System.out.println(ans);
taskFutures.remove(i);
}
}
}

Lets get insights into how execute can be significant. Just because submit returns a Java-Future by default does not mean that execute has a barrier to the Java-Future. There can be an implementation of the same by using non-blocking Java-Future through the use of FutureTask. Oh yeah, before it gets more speculative, lets jump into implementation of the above described prime number solution using execute with FutureTask. The github link to full implementation is given at the end of this article:

List<Integer> listRandomNum =new ArrayList<Integer>();
List<Future<String>> taskFutures =new ArrayList<Future<String>>();
ExecutorService service =Executors.newFixedThreadPool(10);

/**
* Adding randomly generated 9-digit number to the list
*/
for(int i=0; i<4; i++) {
Random rand = new Random();
int number = rand.nextInt(999999999) + 100000000;
listRandomNum.add(number);
}
/**
* Create a FutureTask and use execute on it
*/
for(Integer num:listRandomNum) {
FutureTask<String> taskOfFuture = new FutureTask<String>(new IsPrime(num));
taskFutures.add(taskOfFuture);
service.execute(taskOfFuture);
}

/**
* Use isDone()method of Future to check for the task completeness
* and loop until all the tasks from the list is completed
*/
while(taskFutures.size() != 0) {
for(int i=0; i<taskFutures.size(); i++) {
if(taskFutures.get(i).isDone()) {
String ans = "";
try{
ans = taskFutures.get(i).get();
}
catch(Exception e){
System.err.println(e);
}
System.out.println(ans);
taskFutures.remove(i);
}
}
}

The FutureTask is indeed not only a convenient way to do simple one-off tasks but can be involved in bigger asynchronous systems. There is some change in the design aspect to consider if execute were to be replaced by the submit. Regarding the performance, the below are the timing for the various runs on HP i5 quad-core machine:

Submit:  Four Numbers    -> 6200ms
Eight Numbers -> 12000ms
Execute: Four Numbers -> 5800ms
Eight Numbers -> 11200ms
The timing is approximate (same set of numbers used) and does not show much variations.

At the end of this short story, the execute did flex its muscles and should not be considered limiting. Lets consider few points to note when using execute in the ExecutorService:

  1. The execute could not have been used with callable directly but combining it with FutureTask; same result is achieved.
  2. Do note that while using execute, its important to add the Java-Futures to the tracking list before using execute on it, which is otherwise for submit.
  3. The execute is mostly considered for tasks like the fire-and-forget missiles but this was an effort to show that it could be used for more robust activities.

While all this maybe done, combination of execute with FutureTask-runnable cannot be used simply because runnable is a strict no-return. Do let me know if there is a way to bypass the above. The full implementation to the above demos is at this link. Happy Coding, cheers !!

References:

  1. https://www.baeldung.com/java-runnable-callable
  2. https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html
  3. https://www.journaldev.com/1650/java-futuretask-example-program

--

--

Chirag M. Onkarappa

Been a software developer for 2+ years, major interests include full stack web dev, back-end dev and competitive coding. @https://www.linkedin.com/in/chiragmo/