Java Concurrency - Callable and Future Interface

Hello,

In this blog I am going to explain about callable and future interfaces and how it overcomes the problem faced by runnable interface.

In Java, To achieve concurrency you can write a multi threaded program by extending the Thread Class or by implementing the Runnable Interface.

In most cases people use Runnable interface for implementing the basic functionality of multi-threading because if you extend the Thread class you cannot extend any other class. A Thing to note, Thread class provides you some inbuilt methods like yield(), interrupt(), etc. which is not provided by Runnable interface.

So when you use either of the above 2 options to create a thread and you cannot return the result computed by the thread and the thread cannot throw the exception while executing the thread task. Let's see an example.

public class RunnableTask implements Runnable{
    private int num;

    public RunnableTask(int num){
        this.num = num;
    }
    public void run() {
       int factorial =  IntStream.range(1, num+1).reduce(1, (x,y) -> x*y);
       System.out.println(factorial); //you cannot return this value to main class 
    }
}
public class Main{
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        RunnableTask task = new RunnableTask(5);
        service.execute(task);
        service.shutdown();
    }
}

You can clearly see here that we cannot return the result computed by the thread to main class.

One way to achieve this is to have shared mutable data between the executing threads and read/write to those mutable data has to be synchronized.

The other and more Simpler way to achieve this is by using Callable Interface which is designed for returning the results from the thread executions. The user of callable interface has to implement a call() method . The usage will be more clear after an example.

public class CallableTask implements Callable<Integer> {
    private int num;
    public CallableTask(int num){
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        return IntStream.range(1, num+1).reduce(1, (x, y) -> x*y);
    }
}
public class Main{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newSingleThreadExecutor();
        CallableTask task = new CallableTask(5);
        Future<Integer> result = service.submit(task);
        System.out.println(result.get());
    }
}

We used execute() vs submit() method in our examples above, execute() does not return any results but simply starts the task while submit() will start the task and return the result in Future object.

You can see here on how we are able to retrieve the result from the thread execution in Future interface. Future is a generic instance to retrieve the value from an asynchronous execution. It has multiple methods to check if the task is completed, to cancel the task if pending.

Please note get() method of future class is a blocking method meaning that the execution will stop there if the thread is still under execution. We can avoid this by using isDone() method of future class which tells you if the thread has completed its execution.

Another cool example of efficiency by Callable can be this ,

Let say you want to make multiple calls to single external third party api or you want to call multiple external apis for the same input considering all results are mutually exclusive, you can clearly make this efficient using callable. Here is the code snippet for better understanding :

 public List<Callable<Object>> prepareTasks(RestClient client, List<String> input, String url){
        List<Callable<Object>> tasks = new ArrayList<>();
        input.forEach(request -> {
            tasks.add(() -> client.makeCall(uri, request));
        });
        return tasks;
    }
List<Callable<Object>> tasks = prepareTasks(new RestClient(), inputs, url);
List<Future<Object>> futures = service.invokeAll(tasks);
futures.forEach(System.out::println);

You can observe here that we are calling the third party api for number of different inputs parallelly.

You can also achieve the same thing using FutureTask which implements RunnableFuture and is also present under java.util.concurrent package. When we call submit for a Callable task the AbstractExecutorService internally converts it into a FutureTask only .

We can also say that FutureTask is the base implementation of Future Interface which has all the methods of Future interface in it.

Here is a snippet from AbstractExecutorService,

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

Here is the snippet on how can you execute the very same example as above using FutureTask :

public static List<FutureTask<String>> executeAllTasksAndReturnFuture(RestClient client, List<String> input, String uri, ExecutorService service){
        List<FutureTask<String>> tasks = new ArrayList<>();
        input.forEach(request -> {
            FutureTask<String> task = new FutureTask(() -> client.makeCall(uri, request));
            tasks.add(task);
            service.submit(task);
        });
        return tasks;
    }

//you can get the result from FutureTask as below 
List<FutureTask<String>> tasks = executeAllTasksAndReturnFuture(...);
String result = tasks.get(0).get();  // get the result of api call #01

Here we are calling the same api for multiple different inputs parallelly using FutureTask.

Of both above mentioned 2 approaches which eventually does the same thing under the hood,

  1. Submit Callable to executorService and retrieve the result from Future interface
  2. Submit FutureTask to executorService and retrieve the result from the same instance as it is also a base implementation of Future

I prefer the approach #01 as in that approach conversion of Callable -> FutureTask is done by ExecutorService and not by the programmer which ensures that there are not more than one references exists to a FutureTask instance.

You can refer the detailed discussion here : Future Vs FutureTask