The Runnable interface only provides basic functionality for multithreaded programming. There are many limitations to it. The Callable and Future interfaces are an answer to many of the limitations. This article focuses on these advanced topics and show how to implement them in a Java program.
The Runnable Interface
The Runnable interface is used to designate a class that is intended to be executed by a thread. It has a lone no-argument method called, run (), which must be overridden by the implemented class. The core idea of this interface is to provide a common protocol for objects that want to execute code while they are still in execution—in other words, a running section of code that invokes another section of code without itself being stopped or terminated (simultaneous execution). There is a class called Thread, which provides the same functionality, but with the difference that it has to be sub classed (inheritance) to be used. The reason for the existence of an interface and a class, although, to achieve the same goal is the advice that object-oriented design follows: a class should not be inherited unless we intend to change the meaning of it by manipulating one or more of its methods and augmenting its properties. By implementing the interface counterpart of the Thread class, called Runnable, we can almost wash off the heavy responsibility of extending a class (sub classing it), and yet get the full benefit of thread implementation in our program. The Java API documentation also attunes to the same idea: …in most cases, the Runnable interface should be used if you are planning to override the run () method and no other Thread methods.
Note that multithreaded programming is a vast and complicated area. The Runnable interface only provides the basic functionality. This poses a serious limitation when performing a long operation and the application wants to communicate in between the executions. Because the run method does not return a value, the only way to communicate across the executing thread is to use the shared mutable data. Now, the problem with the shared data is that they must be synchronized for reading and writing among calling threads. The Callable interface tries to fix this limitation.
For the sake of completeness, here is how we may implement Runnable.
package org.mano.example; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class RunnableMain { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService service = Executors .newSingleThreadExecutor(); RunnableFactorialTask task = new RunnableFactorialTask(5); service.execute(task); service.shutdown(); } } class RunnableFactorialTask implements Runnable{ private int num = 0; public RunnableFactorialTask(int num){ this.num = num; } @Override public void run(){ int prod = 1; for (int i = 2; i <= num; i++) prod *= i; System.out.println(prod); } }
The Callable Interface
The Callable interface is designed to define a task that returns a result and may throw an exception. It is declared in the java.util.concurrent package. This interface also contains a single, no-argument method, called call (), to be overridden by the implementors of this interface. This method is similar to the run () method of the Runnable interface, except that it returns a value and can throw a checked exception. In fact, the whole idea is that Callable is nothing but Runnable in the sense that both the interfaces designate a class with the potential to be executed by another thread, except that it is not predisposed with the limitation of Runnable.
Therefore, the use of Callable and Runnable are almost exchangeable except for the reason we talked about. An application that uses Callable often runs concurrently with other classes that either implement the Runnable or Callable interface.
A Quick Comparison
Callable<V> | Runnable |
Part of the java.util.concurrent package since Java 1.5 | Part of the java.lang package since Java 1.0 |
It is a parameterized interface, such as Callable<V> | It is a non-parameterized interface |
Can throw a checked Exception | Cannot throw a checked Exception |
Contains a single method, called call (), which has return Type V, the same as of the defined interface parameter Type | Contains a single method, called run (), which returns void |
Here is an implementation of the preceding program using the Callable interface.
package org.mano.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class CallableMain { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService service = Executors.newSingleThreadExecutor(); CallableFactorialTask task = new CallableFactorialTask(5); Future<Integer> f = service.submit(task); Integer val = f.get(); System.out.println(val); service.shutdown(); } } class CallableFactorialTask implements Callable<Integer> { private int num = 0; public CallableFactorialTask(int num){ this.num = num; } @Override public Integer call() throws Exception { int prod = 1; for (int i = 2; i <= num; i++) prod *= i; return prod; } }
Note that in both programs we have used the ExecutorService interface, which is an extension of the Executor class. An Executor instance helps to manage thread termination and, in our Callable implementation program, produces Future for tracking one or more asynchronous tasks. The invocation to the submit () method executes the Callable argument and returns an object type Future.
The Future Interface
The Future interface is a generic interface that represents the value returned from an asynchronous computation. It contains methods to check if the computation has been completed or wait for it, retrieve the result. In the preceding code, the get() method of Future blocks the calling thread and awaits the Callable to complete its computation and then retrieves the result. This interface also contains methods to cancel Callable‘s execution. However, once the computation has been completed, it cannot be canceled.
Among the many implementations of this interface, the CompletableFuture class, introduced with Java 8, enables us to asynchronously execute Runnable to perform tasks that do not return a value, and the Supplier task that returns a value. The Supplier is a functional interface which contains a single, no-argument method, called get (), and returns a result, like Callable. The CompletableFuture class has various advanced capabilities for programmers to act upon the Future interface. Refer to the Java API documentation for details.
Let’s try another quick example using the Callable and Future interfaces.
A Quick Example
package org.mano.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class AnotherCallableMain { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService service = Executors.newFixedThreadPool(3); Future<Double> f1 = service.submit(new AreaOfACircle(12.5)); Future<Integer> f2 = service.submit(new FactorialTask(7)); Future<Double> f3 = service.submit(new CircumOfACircle(12.5)); System.out.println("Area of Circle (radius 12.5) = " + f1.get()); System.out.println("Factorial of 7 = " + f2.get()); System.out.println("Circumference (radius 12.5) = " + f3.get()); service.shutdown(); } } class AreaOfACircle implements Callable<Double> { double rad = 0.0; public AreaOfACircle(double d) { rad = d; } @Override public Double call() throws Exception { return Math.PI * rad * rad; } } class CircumOfACircle implements Callable<Double> { double rad = 0.0; public CircumOfACircle(double d) { rad = d; } @Override public Double call() throws Exception { return 2 * Math.PI * rad; } } class FactorialTask implements Callable<Integer> { private int num = 0; public FactorialTask(int num) { this.num = num; } @Override public Integer call() throws Exception { int prod = 1; if (num > 1) { for (int i = 2; i <= num; i++) prod *= i; } return prod; } }
Conclusion
The Callable interface is an interesting addition to the Java concurrent API; it fixes the problem of Runnable. A thread created through Callable can return a value. This is a powerful feature and can be used to create a multithreaded program that can return various partly or fully completed computation results simultaneously done by numerous threads. Also, threads can return their status code, indicating successful or unsuccessful computation of its computation.
Reference
Java API Documentation