Sunday, June 5, 2011

Java Executors

The old way of creating threads in Java was to extend the java.lang.Thread class or to implement the java.lang.Runnable interface and pass it to Thread as argument. In this approach, the task is modeled as a Runnable and you create one or more threads for each task. There were no built in facilities to re-use threads such as thread pools. Additionally, once a task was started, there was no easy way to know when the task completed without implementing the wait/notify mechanism.

Since JDK5, another abstraction for concurrent execution of tasks is the Executor interface.
public interface Executor {
   void execute(Runnable cmd) ; 
}
The task to be executed is coded by implementating the Runnable interface, similar to the older model. However in the old model, execution is typically hard coded by extending the java.lang.Thread class.

With the executor framework, the submission and execution is decoupled by using the Executors class to create different kinds of Executor's that can execute the runnable.

The ExecutorService interface extends Executor and provides additional methods that lets callers submit tasks for concurrent execution.

While one can implement Executor or ExecutorService and delegate to Thread to do the actual work, that is generally not the recommended way.

java.util.concurrent.Executors is a class with factory methods to create concrete implementations of ExecutorService. This includes flexible thread pool based implementations. Using thread pools for concurrent execution has the advantage that it lets your application scale.

public static ExecutorService newFixedThreadPool(int nThreads) creates a thread pool that created threads upto the size nThreads. After that it created additional threads only if one of the existing thread dies.

public static ExecutorService newCachedThreadPool() created a threadpool with no upper bound on the number of threads. It will create threads as needed but can reuse existing threads.

public static ExecutorService newSingleThreadExecutor() creates a single thread. If the existing one dies, it will create a new one. But you will never have more that one thread.

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) lets you create a thread pool that can be scheduled to execute periodically.

Let us write a simple concurrent server using fixed thread pool.

1 public class WebServer {
2  private final ExecutorService tpool = Executors.newFixedThreadPool(50) ;
3
4  public void start() throws IOException {
5    ServerSocket socket = new ServerSocket(8080) ;
6    while(!tpool.isShutdown()) {
7        try {
8           Socket c = socket.accept() ;
9           tpool.execute(new Runnable() {
10               public void run() { process(c) ; }
11           }
12        } catch(Exception e) {
13           log("Error occured .." + e) ;
14        }
15    }
16  }
17  private void process(socket c) {
18    // service the request   
19  }
20  public void stop() {
21    tpool.shutdown() ;
22  }
23}
In line 2 we create an ExecutorService. In lines 9-11 we create a runnable for each task and submit it for execution. The shutdown call in line 21 is a graceful shutdown. Tasks that are already submitted will be completed and no new tasks are accepted.

The advantages of using Executors are:
   Decoupling of task creation with submission/excecution.
   Built in thread pools.
   Built in orderly shutdown.
   Built in scheduling. (mentioned above but not discussed)
   Ability to check or block on completion of tasks.

Today, if you needed a data structure such as a list or a Map, you use the classes in the collection framework java.util.*. Similarly for concurrent programming, you should use the executor framework in java.concurrent.* as it gives you lot of function that you would otherwise have to code yourself.