Thursday, January 21, 2021

Java ThreadPoolExecutor - Thread Pooling With ExecutorService

ThreadPoolExecutor class in Java implements both Executor and ExecutorService interfaces and is part of java.util.concurrent package. ThreadPoolExecutor is used to create and manage thread pools and it executes each submitted task using one of possibly several pooled threads.

Using thread pools in Java provide benefits in following ways-

  1. You may see improvement in performance when executing large numbers of asynchronous tasks as overhead of creating thread per task is not there, threads are taken from the maintained thread pool instead.
  2. Thread pools also provide means of managing the resources (by restricting the pool size, having an option to keep thread alive for the given time) consumed when executing a collection of tasks.
  3. ThreadPoolExecutor also maintains some basic statistics, such as the number of completed tasks.

Refer Java ScheduledThreadPoolExecutor - Task Scheduling in Java to see how to schedule a task to run after a given delay, or to execute periodically.

Instantiating a ThreadPoolExecutor

ThreadPoolExecutor class in Java provides many constructors to instantiate a ThreadPoolExecutor-

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)- Creates a new ThreadPoolExecutor with the given initial parameters and default thread factory and rejected execution handler.

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)- Creates a new ThreadPoolExecutor with the given initial parameters.

But it is more convenient to use one of the static factory methods provided by the Executors class providing preconfigured settings for the most common usage scenarios.

  • Executors.newCachedThreadPool()- Creates an unbounded thread pool which creates new threads as needed, but will reuse previously constructed threads when they are available. Threads that have not been used for sixty seconds are terminated and removed from the cache.
  • Executors.newFixedThreadPool(int nThreads)- Creates a thread pool that reuses a fixed number of threads. At any point, at most nThreads threads will be active processing tasks. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread is available.
  • Executors.newSingleThreadExecutor()- Creates an Executor that uses a single worker thread operating off an unbounded queue. Tasks are guaranteed to execute sequentially, and no more than one task will be active at any given time.

Though these methods provide settings for the parameters but it is better to have idea of the parameters as shown in the constructor of the ThreadPoolExecutor class.

  • corePoolSize- The number of threads to keep in the pool. When a new task is submitted and number of threads running is less than the corePoolSize, a new thread is created to handle the request, even if other worker threads are idle. If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.
  • maximumPoolSize- the maximum number of threads to allow in the pool.
  • threadFactory- New threads are created using a ThreadFactory. If not otherwise specified, a Executors.defaultThreadFactory() is used, that creates threads to all be in the same ThreadGroup and with the same NORM_PRIORITY priority and non-daemon status. By supplying a different ThreadFactory, you can alter the thread's name, thread group, priority, daemon status, etc.
  • keepAliveTime- If the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than the keepAliveTime.
  • WorkQueue- The queue used for holding tasks before they are executed. Any BlockingQueue may be used to transfer and hold submitted tasks. It can be a bounded queue or unbounded.
  • RejectedExecutionHandler- Tasks submitted after the Executor has been shut down or the pool is exhausted and can't take new tasks are rejected. In that scenario the execute method invokes the RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor) method of its RejectedExecutionHandler. Four predefined handler policies are provided-
    1. ThreadPoolExecutor.AbortPolicy- The handler throws a runtime RejectedExecutionException upon rejection. It is the default handler policy.
    2. ThreadPoolExecutor.CallerRunsPolicy- Handler runs the rejected task directly in the calling thread of the execute method, unless the executor has been shut down, in which case the task is discarded.
    3. ThreadPoolExecutor.DiscardPolicy- A task that cannot be executed is simply dropped.
    4. ThreadPoolExecutor.DiscardOldestPolicy- The task at the head of the work queue is dropped.

Java ThreadPoolExecutor Examples

1- In the first example let’s create a ThreadPoolExecutor using one of the constructor and providing values for the arguments.

Here a ThreadPoolExecutor instance is created with corePoolSize as 2 and maxPoolSize as 3. ArrayBlockingQueue (a bounded queue) is instantiated as a work queue with the capacity of 3. Default thread factory is used as the thread factory for threads and for rejection handling DiscardPolicy is used.

public class TEDemo {
  public static void main(String[] args) {
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2, 3, 2, 
            TimeUnit.SECONDS, 
            new ArrayBlockingQueue<Runnable>(3), 
            Executors.defaultThreadFactory(), 
            new ThreadPoolExecutor.DiscardPolicy());
    // running 6 tasks
    poolExecutor.execute(new Task());
    poolExecutor.execute(new Task());
    poolExecutor.execute(new Task());
    poolExecutor.execute(new Task());
    poolExecutor.execute(new Task());
    poolExecutor.execute(new Task());
    //shutting down the executor service
    poolExecutor.shutdown();
  }
}

class Task implements Runnable{
  @Override
  public void run() {
    System.out.println("in run task for thread - " + Thread.currentThread().getName());
    // Introducing some delay for switching
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

Output

in run task for thread - pool-1-thread-1
in run task for thread - pool-1-thread-2
in run task for thread - pool-1-thread-3
in run task for thread - pool-1-thread-1
in run task for thread - pool-1-thread-3
in run task for thread – pool-1-thread-2

As you can see queue can hold at most 3 tasks so 3 threads are created for executing the rest of the Runnable tasks.

2- Java example using Executors.newFixedThreadPool. When you create thread pool using Executors.newFixedThreadPool, parameters corePoolSize and MaximumPoolSize have the same value as the int argument passed. WorkQueue used is a LinkedBlockingQueue.
public class TEDemo {

 public static void main(String[] args) {
    // creating executor with pool of 2 threads
    ExecutorService ex = Executors.newFixedThreadPool(2);
    // running 6 tasks
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    //shutting down the executor service
    ex.shutdown();
 }
}

class Task implements Runnable{

  @Override
  public void run() {
   System.out.println("in run task for thread - " + Thread.currentThread().getName());
   // Introducing some delay for switching
   try {
    Thread.sleep(500);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
}

Output

in run task for thread - pool-1-thread-2
in run task for thread - pool-1-thread-1
in run task for thread - pool-1-thread-2
in run task for thread - pool-1-thread-1
in run task for thread - pool-1-thread-1
in run task for thread – pool-1-thread-2
3- Java example using Executors.newCachedThreadPool(). In case of cached threadpool if previously constructed threads is available it is reused, otherwise new threads are created as needed. So the core pool size for cached threadpool is 0 and maxPoolSize=Integer.MAX_VALUE. SynchronousQueue is used as work queue. SynchronousQueue doesn’t have any internal capacity so direct handoff happens.
public class TEDemo {

 public static void main(String[] args) {
    // creating executor with pool of 2 threads
    ExecutorService ex = Executors.newCachedThreadPool();
    // running 6 tasks
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    ex.execute(new Task());
    //shutting down the executor service
    ex.shutdown();
 }
}

class Task implements Runnable{

  @Override
  public void run() {
   System.out.println("in run task for thread - " + Thread.currentThread().getName());
   // Introducing some delay for switching
   try {
    Thread.sleep(2000);
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }
  }
}

Output

in run task for thread - pool-1-thread-6
in run task for thread - pool-1-thread-4
in run task for thread - pool-1-thread-3
in run task for thread - pool-1-thread-5
in run task for thread - pool-1-thread-1
in run task for thread - pool-1-thread-2

As you can see six threads are created for processing six tasks.

That's all for this topic Java ThreadPoolExecutor - Thread Pooling With ExecutorService. If you have any doubt or any suggestions to make please drop a comment. Thanks!


Related Topics

  1. CompletableFuture in Java With Examples
  2. AtomicLong in Java With Examples
  3. CopyOnWriteArrayList in Java With Examples
  4. Java CyclicBarrier With Examples
  5. Java Concurrency Interview Questions And Answers

You may also like-

  1. How to Convert ArrayList to Array in Java
  2. HashMap Vs LinkedHashMap Vs TreeMap in Java
  3. Difference Between sleep And wait in Java Multi-Threading
  4. Enum Type in Java
  5. final Vs finally Vs finalize in Java
  6. instanceof Operator in Java With Examples
  7. Java Program to Check Prime Number
  8. Word Count MapReduce Program in Hadoop