Wednesday, April 23, 2014
To scale applications it becomes necessary to separate thread creation and management from rest of the application. Using thread pools is an approach typically employed in large scale systems. This article provides a quick overview on executor framework in Java and provides examples on how to work with thread pools.

Significance of Thread Pools

Thread Pools use worker threads to minimize thread creation overhead.
Thread Pools reduce the memory management overhead which is important for large scale applications.
Thread Pools allow the applications to degrade gracefully.

Executor Service Objects

Java supports executor framework which provide the enablers for thread creation and management. Listed are some of the Java objects of executor service and their key purpose.

Executor

Executor interface provides a way to decouple tasks from how a task will run, thread to use etc. Executor is normally used instead of creating threads.

ExecutorService

ExecutorService interface represents an asynchronous execution mechanism which is capable of executing tasks in the background.

ThreadPoolExecutor

ExecutorService is an interface and ThreadPoolExecutor is one of the concrete implementations of ExecutorService. Executes submitted tasks using one of the several pooled threads. ThreadPoolExecutor provides many adjustable parameters and hooks. For ease of programming it is recommended to use the static factory methods provided by Executors.

Executors

Provides factory and utility methods for executor. They provide static methods like newFixedThreadPool(), newCachedThreadPool() which are much easier to use.

Approaches for Thread Pools

Java supports several approaches for handling thread pools. These include:

Fixed thread pool

This approach reuses fixed number of threads. At any point at-most "n" threads would be active. If additional tasks are submitted when all threads are active the tasks would be queued. Threads in the pool will exist until it is explicitly shutdown.

Cached thread pool

This approach creates new threads as needed, but will reuse previously constructed threads when they are available. If no threads are available for a task a new thread will be created and added to the pool. Threads that are not used for 60 secs will be terminated and removed from the cache. 

Single thread pool

This approach uses a single worker thread. Tasks submitted would be executed sequentially. This can be assumed equivalent to a fixed thread pool of size "1". The primary difference is that fixed thread pool can reconfigured to use additional threads but single thread pool is not re-configurable.

Example of Thread Pool usage

This example shows usage of fixed, cached and single thread pool.
package com.sourcetricks.threadpool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPool {
 
 class MyThread extends Thread {

  private int id;
  
  public MyThread(int id) {
   this.id = id;
  }
  
  public void run() {
   System.out.println("Starting thread " + id);
   doSomeWork();
   System.out.println("Completed thread " + id);
  }
  
  private void doSomeWork() {
   try {
    Thread.sleep(5000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  }
 }
 
 // Not using thread pool
 private void doWithoutThreadPool() {
  for ( int i = 0; i < 20; i++ ) {
   MyThread thread = new MyThread(i);
   thread.start();
  }
 }
 
 // Using fixed thread pool
 private void doWithFixedThreadPool1() throws InterruptedException {
  ExecutorService executor = Executors.newFixedThreadPool(5);
  for ( int i = 0; i < 20; i++ ) {
   MyThread thread = new MyThread(i);
   executor.execute(thread);
  }
  System.out.println("Active thread count = " + ((ThreadPoolExecutor)executor).getActiveCount());
  
  // Don't accept new work
  executor.shutdown();
  
  // Wait for 30 secs for the threads to complete
  executor.awaitTermination(30, TimeUnit.SECONDS);
 }
 
 // Using fixed thread pool. Directly uses the ThreadPoolEexcutor
 // which provides finer control
 private void doWithFixedThreadPool2() throws InterruptedException {
  int core = 5;
  int max = 10;
  int keepalive = 5000;
  
  ExecutorService executor = new ThreadPoolExecutor(
    core, 
    max, 
    keepalive, 
    TimeUnit.MILLISECONDS, 
    new LinkedBlockingQueue<Runnable>());
  
  for ( int i = 0; i < 20; i++ ) {
   MyThread thread = new MyThread(i);
   executor.execute(thread);
  }
  
  System.out.println("Active thread count = " + ((ThreadPoolExecutor)executor).getActiveCount());
  executor.shutdown();
  executor.awaitTermination(30, TimeUnit.SECONDS);
 }
 
 // Using cached thread pool
 private void doWithCachedThreadPool() throws InterruptedException {
  ExecutorService executor = Executors.newCachedThreadPool();  
  for ( int i = 0; i < 20; i++ ) {
   MyThread thread = new MyThread(i);
   executor.execute(thread);
  }
  System.out.println("Active thread count = " + ((ThreadPoolExecutor)executor).getActiveCount());
 }
 
 // Using single thread pool
 private void doWithSingleThreadPool() throws InterruptedException {
  ExecutorService executor = Executors.newSingleThreadExecutor();
  // Executor returned is not reconfigurable
  for ( int i = 0; i < 20; i++ ) {
   MyThread thread = new MyThread(i);
   executor.execute(thread);
  }
 }

 public static void main(String[] args) {
  
  ThreadPool threadPool = new ThreadPool();
  try {
   threadPool.doWithCachedThreadPool();
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
 }
}

0 comments :

Post a Comment

Contact Form

Name

Email *

Message *

Back to Top