Preface
In java’s multi-threaded development Runnable has always been the core of multi-threading, and Callable is an enhanced version of java 1.5 added in.
In this article, we will explore the differences between Runnable and Callable in detail.
Operation mechanism
First look at the interface definition of Runnable and Callable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
|
1
2
3
4
5
6
7
8
9
10
|
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
|
Runnable needs to implement the run() method and Callable needs to implement the call() method.
We all know that there are two ways to customize a Thread, one is to inherit Thread, but to implement the Runnable interface, this is because Thread itself is a Runnable implementation.
1
2
3
4
5
6
7
|
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
|
So Runnable can be executed by Runnable and the ExecutorService we introduced before, while Callable can only be executed by ExecutorService.
Difference in return values
According to the definition of the two interfaces above, Runnable does not return a value, while Callable can return a value.
If we both submit through the ExecutorService, see the difference.
1
2
3
4
5
|
public void executeTask() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
future = executorService.submit(()->log.info("in runnable!!!!")) ;
executorService.shutdown();
}
|
1
2
3
4
5
6
7
8
|
public void executeTask() {
ExecutorService executorService = Executors.newSingleThreadExecutor();
future = executorService.submit(()->{
log.info("in callable !!!!") ;
return "callable";
});
executorService.shutdown();
}
|
Although we both return Future, the runnable case Future will not contain any value.
Exception handling
Runnable’s run() method definition does not throw any exceptions, so any Checked Exceptions need to be handled in the run() implementation method itself.
Callable’s call() method throws throws Exception, so Checked Exception can be caught outside of the call() method. let’s look at the handling of exceptions in Callable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public void executeTaskWithException(){
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(()->{
log.info("in callable!!!!") ;
throw new CustomerException("a customer Exception");
});
try {
Object object= future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
e.getCause();
}
executorService.shutdown();
}
```
In the above example, we threw a custom CustomerException in the Callable.
This exception will be included in the returned Future. When we call the future.get() method, the ExecutionException will be thrown, and the specific exception information contained in it will be available through e.getCause().
|