Runnable, Callable, FutureTask, ExecutorService and thread pool

Some notes on Java concurrency, starting with Runnable vs Callable:

(1), Runnable.run() does not return a value; Callable<T>.run() returns a value of type T. Callable<T> is a parameterized type whose type parameter indicates the return type of its run method. Runnable cannot be parameterized.

(2), Runnable.run() cannot throw any checked exception; Callable<T>.run() can throw checked and unchecked exceptions.

(3), so it seems Callable is more powerful than Runnable. But you cannot just replace all use of Runnable with Callable. For example, in new Thread(runnable). You cannot instantiate a new thread with Callable.

(4), Callable is introduced in Java SE 5, as part of java.util.concurrent library. Callable is mainly intended to use with ExecutorService, which is a higher level service than the direct manipulation of Thread. Both Runnable and Callable can be submitted to ExecutorService for (usually asynchronous) execution.

(5), java.util.concurrent.Executors (a util class similar to Collections, Arrays) has methods to upgrade a Runnable to Callable, but not the other way.
Callable<Object> callable(Runnable task)
<T> Callable<T> callable(Runnable task, T result)
The Callable converted from callable(Runnable) returns null. The Callable converted from callable(Runnable, T) returns T. However, this return value that was passed in by the client is not the real result of executing task. Its purpose is to tell the client that the return type will be Future<T>. In order to obtain the real result, the client still needs to arrange for a thread-safe shared data storage, e.g., a final or volatile field of Runnable impl. Therefore, the converted Callable is still a Runnalbe in its core.

(6) FutureTask is another artifact involving both Callable and Runnable. FutureTask class implements RunnableFuture interface, which in turn extends both Runnable and Future interfaces. So FutureTask is a Runnable by inheritance, and can be passed to new Thread(futureTask), or ExecutorService.submit(futureTask).

FutureTask has a Callable<V> by composition as FutureTask's constructor takes Callable<V> as a param. FutureTask also has another constructor that takes 2 params (Runnable, V), which are simply converted to Callable via Executors.callable(Runnable, V). In fact, the same pattern is employed in java.util.concurrent wherever a method is overloaded with (Callable) and (Runnable, V).

(7), Runnable and Callable run() method takes no param. Why? For one thing, there is no way to know which types are to be passed in. The best we can do is probably Object[] if we were to add params to it. More importantly, it would export state from the calling thread to the task thread, and entails synchronization between the 2 sides. I guess this is why the plain old Runnable.run() takes no param and returns void. In Java 5 to balance simplicity and usefulness, Callable was added to return a result.

(8), how can a task be performed without any params passed to run()? The typical answer is to pass any required data in when instantiating the Callable or Runnable impl class via its constructor. For example,
public class RunnableImpl implements Runnable {
private final List<String> names;

public RunnableImpl(List<String> names) {
this.names = names;
}

public void run() {...}
}
If RunnableImpl is a static inner class, its run() method can directly reference any static fields in the enclosing class. If RunnableImpl is an instance inner class, its run() method can directly reference any static and instance fields in the enclosing class. If RunnableImpl is an anonymous inner class, its run() method can directly reference any static and instance fields of the enclosing class, and any final local variables in the enclosing method.

(9), you can call Thread.setUncaughtExceptionHandler, or Thread.setDefaultUncaughtExceptionHandler to handle exceptions from Runnable.run(). When using ExecutionService where you have no direct control of threads, you can use a custom ThreadFactory. Executors methods newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor, newSingleThreadScheduledExecutor all take a custom ThreadFactory.

(10), Worker thread in thread pools and regular thread are different. A regular thread dies after its run method terminates and cannot be restarted. A worker thread does not die after the completion of the assigned task; it goes back to the thread pool. The main run method of a work thread is an infinite loop: taking a task from the work queue, and invoke the task's run method to do the task.

(11), ThreadLocal variables should be cleared up by the client once the task is done. Otherwise the left-over reference in worker threads prevent the referenced objects from being garbage-collected. Over time it may cause OutOfMemoryError.

(12), The thread pool default work queue in ExecutionService framework is unbounded. It means once the corePoolSize is reached, all subsequent requests will go into this unbound BlockingQueue, until some busy worker threads complete its task. No new worker threads will be created beyond corePoolSize, and maximumPoolSize is ignored in this case. For more details, see bug 6756747: java.util.concurrent.ThreadPoolExecutor doesn't create new worker when it should.

Followers

Pageviews Last 7 Days