This is part 6 of this series. If you missed any of the last five, you can find them here:
- Part 1 – Overview
- Part 2 – Lifecycle of threads
- Part 3 – Daemon threads
- Part 4 – Interrupting threads
- Part 5 – Thread synchronization
Runnable vs Callable
In this post we’ll start looking at the some of the classes in the Concurrency API. But first we’ll revise what we know about the Runnable
interface, and then introduce the Callable
interface.
As we saw from the previous posts, creating running and controlling threads manually is tedious and error-prone. Running more than a few threads and keeping track of them can become problematic. This gets even more difficult to handle if we want to get results from the running threads. How can we easily do this if the run()
method from the Runnable
doesn’t even allow us to return a value directly?
Java 5 introduced the Concurrency API to simplify concurrent programming. It contains classes providing thread functionality that is tedious and/or difficult to implement in our own code, and makes it much easier to work with threads. Nearly every Java release since version 5 has added extra classes and methods to the API to make it even easier to use concurrency in our own code. The Concurrency API is in the java.util.concurrent
package and its sub-packages.
Runnable Revision
Remember that there are two basic ways to create a class that can be run as a background thread: we can either extend the Thread
class or we can implement the Runnable
interface. The Thread
class has a run()
method which we must then override to get our required functionality. The Runnable
interface defines a run()
method which we need to implement.
The preferred way to create a thread is to implement the Runnable
interface in an existing class. Here is the Runnable
interface:
@FunctionalInterface
public interface Runnable {
public void run();
}
Implementing Runnable
is easy:
public class MyClass extends SomeOtherClass implements Runnable {
// data and code relevant to MyClass
// implementing the run() method of the Runnable interface
@Override
public void run() {
// statements;
}
}
The Runnable
interface is a functional interface with a single abstract method. As such, a Runnable
variable can be used as the assignment target for a lambda expression or a method reference.
Let’s create a lambda expression and assign it to a Runnable
variable. We can add this code to a main()
method somewhere to test it:
Runnable task = () -> {
String threadName = Thread.currentThread().getName();
System.out.println("Hello from " + threadName);
};
// this runs in the main thread, and not as a background thread
task.run();
// this runs as a separate background thread
Thread thread = new Thread(task);
thread.start();
System.out.println("Done!");
The result on the console might look like this:
Hello main
Hello Thread-0
Done!
Or like this:
Hello main
Done!
Hello Thread-0
Due to the two threads running concurrently, we can’t predict if the Runnable
object will be invoked before or after the main thread prints “Done!”. The order is non-deterministic. This means we cannot determine beforehand what the result will be. This is what makes concurrent programming so complex.
Callable Interface
The Runnable
interface is very useful, but it has a few limitations: the run()
method cannot return a value, nor can it throw any exceptions. Both are primary ways that we use to check whether a method is successful in doing whatever it needs to do. A method usually returns a value if successful, or throws an exception if not.
This is where the Callable
interface comes into the picture. The Callable
interface represents a task that can return a result and can throw an exception. Here is the Callable
interface:
@FunctionalInterface
public interface Callable <V> {
public V call() throws Exception;
}
The Callable
interface is similar to Runnable
inasmuch as instances of either can be executed by a thread. The difference is that a Callable
object can return a parameterized result and can throw an exception.
If we implement this interface, we must define a single method with no arguments called call()
returning the parameterized value.
Implementing Callable
is almost as easy as implementing Runnable
. Here is an example of a call()
method returning an int
value wrapped as an Integer
:
import java.util.concurrent.Callable;
public class MyClass extends SomeOtherClass implements Callable<Integer> {
// data and code relevant to MyClass
// implementing the call() method of the Callable interface
@Override
public Integer call() {
// statements;
return 42; // using auto-boxing
}
}
Because Callable
is a functional interface, we can also use a lambda expression to define the work to be done by the Callable
instance. The following Callable
lambda expression returns an Integer
after sleeping for a second. This is to simulate a long running task.
Callable<Integer> task = () -> {
try {
Thread.sleep(1000); // 1000 milliseconds
return 42;
}
catch (InterruptedException e) {
throw new IllegalStateException("Task interrupted!", e);
}
};
Running a Callable Object
As we’ve already seen, Runnable
objects can be passed to Thread
constructors to be run. Callable
objects are different. They cannot be run directly by thread instances. They need to be submitted to an ExecutorService
implementation to be run.
In the next post we’ll look at implementations of the Executor
and ExecutorService
interfaces, and how to submit Callable
tasks to them. We’ll also look into the future with the Future
interface. A Future
wraps the returned value of a Callable
task which still has to run.
If you’ve been following this series, I hope you are feeling more confident about threads. Please share your comments and questions.
Until then, stay safe and keep learning!