A Technology Blog About Code Development, Architecture, Operating System, Hardware, Tips and Tutorials for Developers.

Wednesday, July 11, 2012

Java 5 Executor Framework

5:01:00 PM Posted by Satish , , , No comments

A thread pool is a collection of runnables with a work queue. The threads in the pool constantly run and check the work queue for new work. If there is new work to be done they execute this Runnable.

In Java 5, Executor framework was introduced with the java.util.concurrent.Executor interface. This was introduced to fix some of the shortcomings discussed below.

1. The Executor framework is a framework for standardizing invocation, scheduling, execution, and control of asynchronous tasks according to a set of execution policies.



2. Even though the threads are light-weighted than creating a process, creating them utilizes a lot of resources. Also, creating a new thread for each task will consume more stack memory as each thread will have its own stack and also the CPU will spend more time in context switching. Creating a lot many threads with no bounds to the maximum threshold can cause application to run out of heap memory. So, creating a ThreadPool is a better solution as a finite number of threads can be pooled and reused. The runnable or callable tasks will be placed in a queue, and the finite number of threads in the pool will take turns to process the tasks in the queue.

Here is the sample code:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class Sum  implements Runnable {
  
    private static final int NO_OF_THREADS= 3;
  
    int maxNumber;
  
    public Sum(int maxNumber) {
       this.maxNumber = maxNumber;
    }
  
    /** method where the thread execution will start **/
    public void run(){
        int sum = 0;
        for (int i = 0; i = maxNumber; i++) {
           sum += maxNumber;
        } 
         
        System.out.println("Thread " + Thread.currentThread().getName() + " count is " + sum);
    }
     
     
    /** main thread. Always there by default. **/
    public static void main(String[] args) {
       ExecutorService executor = Executors.newFixedThreadPool(NO_OF_THREADS);   // create a pool of 3 threads
       for (int i = 10000; i < 10100; i++) {
          Runnable worker = new Sum(i);               // create worker threads
          executor.execute(worker);                   // add runnables to the work queue 
       }
   
       // This will make the executor accept no new threads
       // and finish all existing threads in the queue
       executor.shutdown();
   
       // Wait until all threads have completed
       while (!executor.isTerminated()) {
 
       }
   
       System.out.println("Finished all threads");
    }
 
}


3. The Runnable interface's void run( ) method has no way of returning any result back to the main thread. The executor framework introduced the Callable interface that returns a value from its call( ) method. This means the asynchronous task will be able to return a value once it is done executing.

import java.util.ArrayList;
import java.util.List;
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 Sum  implements Callable<String> {
  
 private static final int NO_OF_THREADS = 3;
  
 int maxNumber;
  
 public Sum(int maxNumber) {
    this.maxNumber = maxNumber;
 }
  
  /** method where the thread execution will start
    *  this can return a value
    */
    public String call(){
        int sum = 0;
        for (int i = 0; i <= maxNumber; i++) {
            sum += maxNumber;
        } 
         
        return Thread.currentThread().getName() + " count is " + sum;
    }
     
     
    /** main thread. Alwyas there by default. **/
    public static void main(String[] args) {
      ExecutorService executor = Executors.newFixedThreadPool(NO_OF_THREADS);                       // create a pool of 3 threads
      List<Future<String>> list = new ArrayList<Future<String>>(10);  // provides facility to return results asynchronously
      
      for (int i = 10000; i < 10100; i++) {
        Callable<String> worker = new Sum(i);                 // create worker threads 
        Future<String> submit = executor.submit(worker);      // add callables to the work queue
        list.add(submit);                                            // provides facility to return results asynchronously
      }
   
      //process the results asynchronously when each thread completes its task
      for (Future<String> future : list) {
        try {
            System.out.println("Thread " + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
           e.printStackTrace();
        }
      }
   
   
      executor.shutdown();
   
      System.out.println("Finished all threads");
   }
 
}

Output
Thread pool-1-thread-1 count is 100010000
Thread pool-1-thread-2 count is 100030002
Thread pool-1-thread-3 count is 100050006
Thread pool-1-thread-1 count is 100070012
Thread pool-1-thread-1 count is 100090020



4. The various Executor implementations provide different execution policies to be set while executing the tasks. For example, the ThreadPool supports the following policies:

newFixedThreadPool: Creates threads as tasks are submitted, up to the maximum pool size, and then attempts to keep the pool size constant.
newCachedThreadPool: Can add new threads when demand increases, no bounds on the size of the pool.
newSingleThreadExecutor: Single worker thread to process tasks, Guarantees order of execution based on the queue policy (FIFO, LIFO, priority order).
newScheduledThreadPool: Fixed-size, supports delayed and periodic task execution.

5. The ExecutorService provides facilities to shut down an application gracefully, abruptly, or somewhere in-between.

The Executor is based on the producer-consumer design pattern, where threads that submit tasks are producers and the threads that execute tasks are consumers. In the above examples, the main thread is the producer as it loops through and submits tasks to the worker threads. The "Sum" (i.e. a worker thread) is the consumer that executes the tasks submitted by the main (i.e. consumer) thread.

0 comments:

Post a Comment