June 28, 2022

Java ThreadPoolExecutor - Thread Pool with ExecutorService

ThreadPoolExecutor in Java is used to execute each submitted task using one of possibly several pooled threads. When an instance of ThreadPoolExecutor is created a thread pool is also created and one of the thread from this pool of threads is used to execute tasks.

Java ThreadPoolExecutor

ThreadPoolExecutor class is part of Java Executor framework with in the Java concurrent API. This class implements both Executor and ExecutorService interfaces.

Thread pool in ThreadPoolExecutor

ThreadPoolExecutor uses threads from a thread pool to execute tasks. Advantages you get by using thread pool are-

  1. Pooled thread exists separately from the Runnable and Callable tasks it executes and is often used to execute multiple tasks.
  2. Thread objects use a significant amount of memory. In a large-scale application if each task uses its own thread then allocating and deallocating many thread objects creates a significant memory management overhead. Using pooled threads minimizes the overhead due to thread creation.
  3. Using a thread pool also provides a means of bounding and managing the resources. You can have a bounded pool of threads. You can also configure the keepAliveTime for a thread so that threads are terminated if there are not many tasks thus reducing the overall pool size.
  4. Apart from these advantages provided through thread pooling, ThreadPoolExecutor also maintains some basic statistics like the number of completed tasks, number of threads that are actively executing tasks.

Java ThreadPoolExecutor constructors

ThreadPoolExecutor class has 4 constructors that can be used to get an instance of ThreadPoolExecutor.

  • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)- Creates a new ThreadPoolExecutor with the given initial parameters, the default thread factory and the default rejected execution handler.
  • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)- Creates a new ThreadPoolExecutor with the given initial parameters and default thread factory.
  • ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)- Creates a new ThreadPoolExecutor with the given initial parameters and default 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.

The parameters used in these constructors of the ThreadPoolExecutor class are as follows-

  • corePoolSize- The number of threads to keep in the pool. These number of threads will anyway be created even if they are idle, unless allowCoreThreadTimeOut is set.
  • maximumPoolSize- The maximum number of threads allowed to be in the pool.
  • keepAliveTime- When the number of threads is greater than the corePoolSize in the thread pool, keepAliveTime is the maximum time that excess idle threads will wait for new tasks before terminating.
  • unit - the time unit for the keepAliveTime argument
  • workQueue- The queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method. The queue used can be a bounded or unbounded blocking queue.
  • handler- the handler to use when execution is blocked because the thread bounds and queue capacities are reached.
  • threadFactory- the factory to use when the executor creates a new thread.

Creating ThreadPoolExecutor instance using Executors factory methods

Rather than creating instances of ThreadPoolExecutor directly using one of the above constructor, you can use static factory methods provided by the Executors class to get a ThreadPoolExecutor.

  • newCachedThreadPool()- Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available.
  • newCachedThreadPool(ThreadFactory threadFactory)- Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available, and uses the provided ThreadFactory to create new threads when needed.
  • newFixedThreadPool(int nThreads)- Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
  • newFixedThreadPool(int nThreads, ThreadFactory threadFactory)- Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue, using the provided ThreadFactory to create new threads when needed.
  • newSingleThreadExecutor()- Creates an Executor that uses a single worker thread operating off an unbounded queue.
  • newSingleThreadExecutor(ThreadFactory threadFactory)- Creates an Executor that uses a single worker thread operating off an unbounded queue, and uses the provided ThreadFactory to create a new thread when needed.

Java ThreadPoolExecutor example using constructor

If you chose to create ThreadPoolExecutor instance yourself and initialize it using a constructor by passing parameters.

public class ExecutorExp {
  public static void main(String[] args) {
    // creating executor with core pool of 2 threads. max pool is 4
    //, keep alive time- 5 secs
    ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 5, 
                  TimeUnit.SECONDS, 
                  new ArrayBlockingQueue<Runnable>(3), 
                  Executors.defaultThreadFactory(), 
                  new ThreadPoolExecutor.DiscardOldestPolicy());
    for(int i = 0; i < 6; i++)
      executor.execute(new Task());
    
    executor.shutdown();
  }
}
class Task implements Runnable{
  @Override
  public void run() {
    System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
    // delay to keep the thread busy
    // so that pool is used
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-3
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-3
Executing task (thread name)- pool-1-thread-1

As you can see using sleep method thread is kept busy so apart from the core pool of 2 threads, one more thread is created (max pool size is 4) to execute tasks.

Java ThreadPoolExecutor example using Executors factory methods

1- Java example using Executors.newSingleThreadExecutor() which uses a single worker thread.

public class ExecutorExp {
  public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    for(int i = 0; i < 4; i++) {
      executor.execute(new Task());	
    }
    executor.shutdown();
  }
}

class Task implements Runnable{
  @Override
  public void run() {
    System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
    // delay to keep the thread busy
    // so that pool is used
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-1

As you can see a single thread executes all the 4 tasks.

2- Java example using Executors.newFixedThreadPool. This method creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue. When you use this method, internally Executors class creates a ThreadPoolExecutor instance using the following parameters-

new ThreadPoolExecutor(nThreads, nThreads,
          0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue());
public class ExecutorExp {
  public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(2);
    for(int i = 0; i < 4; i++) {
      executor.execute(new Task());	
    }
    executor.shutdown();
  }
}
class Task implements Runnable{
  @Override
  public void run() {
    System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
    // delay to keep the thread busy
    // so that pool is used
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-1
As you can see 2 threads are used to execute the submitted tasks.

3- Java example using Executors.newCachedThreadPool(). This method creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. When you use this method, internally Executors class creates a ThreadPoolExecutor instance using the following parameters-

new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                       60L, TimeUnit.SECONDS,
                       new SynchronousQueue<Runnable>());
public class ExecutorExp {

  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();
    for(int i = 0; i < 4; i++) {
      executor.execute(new Task());	
    }
    executor.shutdown();
  }
}
class Task implements Runnable{
  @Override
  public void run() {
    System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
    // delay to keep the thread busy
    // so that pool is used
    try {
      Thread.sleep(500);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}
Output
Executing task (thread name)- pool-1-thread-3
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-4
Executing task (thread name)- pool-1-thread-1
As you can see 4 new threads are used for 4 submitted tasks.

That's all for the topic Java ThreadPoolExecutor - Thread Pool with ExecutorService. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment