Welcome to the fourth part of my tutorial series on Java Concurrency. In earlier tutorials, we learned the basics of concurrency, threads, runnables and executor services. In this tutorial, we’ll learn about Callable and Future.
Callable
In the previous tutorials, we used a Runnable
object to define the tasks that are executed inside a thread. While defining tasks using Runnable
is very convenient, it is limited by the fact that the tasks can not return a result.
What if you want to return a result from your tasks?
Well, Java provides a Callable
interface to define tasks that return a result. A Callable
is similar to Runnable
except that it can return a result and throw a checked exception.
Callable
interface has a single method call()
which is meant to contain the code that is executed by a thread. Here is an example of a simple Callable -
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
// Perform some computation
Thread.sleep(2000);
return "Return some result";
}
};
Note that with Callable
, you don’t need to surround Thread.sleep()
by a try/catch block, because unlike Runnable, a Callable can throw a checked exception.
You can also use a lambda expression with Callable like this -
Callable<String> callable = () -> {
// Perform some computation
Thread.sleep(2000);
return "Return some result";
};
Executing Callable tasks using ExecutorService and obtaining the result using Future
Just like Runnable
, you can submit a Callable
to an executor service for execution. But what about the Callable’s result? How do you access it?
The submit()
method of executor service submits the task for execution by a thread. However, it doesn’t know when the result of the submitted task will be available. Therefore, it returns a special type of value called a Future
which can be used to fetch the result of the task when it is available.
The concept of Future
is similar to Promise in other languages like Javascript. It represents the result of a computation that will be completed at a later point of time in future.
Following is a simple example of Future and Callable -
import java.util.concurrent.*;
public class FutureAndCallableExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<String> callable = () -> {
// Perform some computation
System.out.println("Entered Callable");
Thread.sleep(2000);
return "Hello from Callable";
};
System.out.println("Submitting Callable");
Future<String> future = executorService.submit(callable);
// This line executes immediately
System.out.println("Do something else while callable is getting executed");
System.out.println("Retrieve the result of the future");
// Future.get() blocks until the result is available
String result = future.get();
System.out.println(result);
executorService.shutdown();
}
}
# Output
Submitting Callable
Do something else while callable is getting executed
Retrieve the result of the future
Entered Callable
Hello from Callable
ExecutorService.submit()
method returns immediately and gives you a Future. Once you have obtained a future, you can execute other tasks in parallel while your submitted task is executing, and then use future.get()
method to retrieve the result of the future.
Note that, the get()
method blocks until the task is completed. The Future
API also provides an isDone()
method to check whether the task is completed or not -
import java.util.concurrent.*;
public class FutureIsDoneExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> {
Thread.sleep(2000);
return "Hello from Callable";
});
while(!future.isDone()) {
System.out.println("Task is still not done...");
Thread.sleep(200);
}
System.out.println("Task completed! Retrieving the result");
String result = future.get();
System.out.println(result);
executorService.shutdown();
}
}
# Output
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task completed! Retrieving the result
Hello from Callable
Cancelling a Future
You can cancel a future using Future.cancel()
method. It attempts to cancel the execution of the task and returns true if it is cancelled successfully, otherwise, it returns false.
The cancel()
method accepts a boolean argument - mayInterruptIfRunning
. If you pass the value true
for this argument, then the thread that is currently executing the task will be interrupted, otherwise in-progress tasks will be allowed to complete.
You can use isCancelled()
method to check if a task is cancelled or not. Also, after the cancellation of the task, isDone()
will always true.
import java.util.concurrent.*;
public class FutureCancelExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newSingleThreadExecutor();
long startTime = System.nanoTime();
Future<String> future = executorService.submit(() -> {
Thread.sleep(2000);
return "Hello from Callable";
});
while(!future.isDone()) {
System.out.println("Task is still not done...");
Thread.sleep(200);
double elapsedTimeInSec = (System.nanoTime() - startTime)/1000000000.0;
if(elapsedTimeInSec > 1) {
future.cancel(true);
}
}
System.out.println("Task completed! Retrieving the result");
String result = future.get();
System.out.println(result);
executorService.shutdown();
}
}
# Output
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task is still not done...
Task completed! Retrieving the result
Exception in thread "main" java.util.concurrent.CancellationException
at java.util.concurrent.FutureTask.report(FutureTask.java:121)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at FutureCancelExample.main(FutureCancelExample.java:34)
If you run the above program, it will throw an exception, because future.get()
method throws CancellationException
if the task is cancelled. We can handle this fact by checking whether the future is cancelled before retrieving the result -
if(!future.isCancelled()) {
System.out.println("Task completed! Retrieving the result");
String result = future.get();
System.out.println(result);
} else {
System.out.println("Task was cancelled");
}
Adding Timeouts
The future.get()
method blocks and waits for the task to complete. If you call an API from a remote service in the callable task and the remote service is down, then future.get()
will block forever, which will make the application unresponsive.
To guard against this fact, you can add a timeout in the get()
method -
future.get(1, TimeUnit.SECONDS);
The future.get()
method will throw a TimeoutException
if the task is not completed within the specified time.
invokeAll
Submit multiple tasks and wait for all of them to complete.
You can execute multiple tasks by passing a collection of Callables to the invokeAll()
method. The invokeAll()
returns a list of Futures. Any call to future.get()
will block until all the Futures are complete.
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
public class InvokeAllExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Callable<String> task1 = () -> {
Thread.sleep(2000);
return "Result of Task1";
};
Callable<String> task2 = () -> {
Thread.sleep(1000);
return "Result of Task2";
};
Callable<String> task3 = () -> {
Thread.sleep(5000);
return "Result of Task3";
};
List<Callable<String>> taskList = Arrays.asList(task1, task2, task3);
List<Future<String>> futures = executorService.invokeAll(taskList);
for(Future<String> future: futures) {
// The result is printed only after all the futures are complete. (i.e. after 5 seconds)
System.out.println(future.get());
}
executorService.shutdown();
}
}
# Output
Result of Task1
Result of Task2
Result of Task3
In the above program, the first call to future.get()
statement blocks until all the futures are complete. i.e. the results will be printed after 5 seconds.
invokeAny
Submit multiple tasks and wait for any one of them to complete
The invokeAny()
method accepts a collection of Callables
and returns the result of the fastest Callable. Note that, it does not return a Future.
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.*;
public class InvokeAnyExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
Callable<String> task1 = () -> {
Thread.sleep(2000);
return "Result of Task1";
};
Callable<String> task2 = () -> {
Thread.sleep(1000);
return "Result of Task2";
};
Callable<String> task3 = () -> {
Thread.sleep(5000);
return "Result of Task3";
};
// Returns the result of the fastest callable. (task2 in this case)
String result = executorService.invokeAny(Arrays.asList(task1, task2, task3));
System.out.println(result);
executorService.shutdown();
}
}
# Output
Result of Task2
Conclusion
You can find the all the code snippets used in this tutorial in my github repository. I encourage you to fork the repo and practice the programs yourself.
Don’t forget to check out the next post in this tutorial series for learning about various issues related to concurrent programs and how to avoid them.
Thank you for reading. Please ask any questions in the comment section below.