Understanding ThreadPoolExecutor, Executors Factory Class, and Optimal Thread Pool Sizing in Java

Introduction to Thread Pools

Thread Pool: A thread management technology based on the concept of pooling, designed to reuse threads, conveniently manage threads and tasks, and decouple thread creation from task execution. In Java, thread pools are primarily created using the ThreadPoolExecutor class, and the JDK also provides the Executors factory class for creating thread pools (though its use is not recommended).

Core Principles:

  1. Create an empty thread pool.
  2. When a task is submitted, the thread pool creates a new thread object. After the task completes, the thread is returned to the pool. For subsequent task submissions, existing threads can be reused without creating new ones.
  3. If there are no idle threads in the pool and new threads cannot be created when a task is submitted, the task will wait in a queue.

Reasons for Using Thread Pools:

Drawbacks of Traditional Multithreading:

  • Drawback 1: Threads are created when needed and destroyed after use. Each task requires creating a new thread, which incurs high overhead for the system.
  • Drawback 2: Uncontrolled risk, as there is no centralized management for created threads, making it difficult to track their status.

Advantages of Thread Pools:

  • (1) Reduce resource consumption by reusing existing threads to minimize the overhead of creating and destroying threads.
  • (2) Improve response speed, as tasks can be executed immediately without waiting for thread creation.
  • (3) Enhance thread manageability, allowing for unified allocation, optimization, and monitoring of threads.

Thus, using thread pools improves system performance and reliability, reduces resource waste, and enhances concurrency.

Using the Executors Factory Class to Create Thread Pools

Steps to Create a Thread Pool with Executors:

  1. Create a thread pool.
  2. Submit tasks.
  3. After all tasks are completed, shut down the thread pool.

Executors is a utility class that returns different types of thread pool objects through its methods.

Method Name Description
public static ExecutorService newCachedThreadPool() Creates an unbounded thread pool.
public static ExecutorService newFixedThreadPool(int nThreads) Creates a bounded thread pool.

Additional Notes:

  • newCachedThreadPool(): Creates a cacheable, unbounded thread pool. Idle threads are reclaimed if they exceed processing needs; if none are available, new threads are created. Threads idle for over 60 seconds are automatical reclaimed. The pool size can grow up to Integer.MAX_VALUE, effectively unlimited.
  • newFixedThreadPool(int nThreads): Creates a fixed-size thread pool, controlling the maximum number of concurrent threads. Excess tasks wait in a LinkedBlockingQueue.

Example of Using Executors to Create a Thread Pool:

Task Object TaskRunnable Class:

public class TaskRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("Thread " + Thread.currentThread().getName() + " prints " + i);
        }
    }
}

Example of newCachedThreadPool() Method

Main Method:

public static void main(String[] args) {
    // 1. Obtain thread pool object
    ExecutorService cachedPool = Executors.newCachedThreadPool();
    // 2. Submit tasks
    cachedPool.submit(new TaskRunnable());
    cachedPool.submit(new TaskRunnable());
    cachedPool.submit(new TaskRunnable());
    cachedPool.submit(new TaskRunnable());
    cachedPool.submit(new TaskRunnable());
    // Destroy the thread pool (typically not done)
    // cachedPool.shutdown();
}

Result: This method creates many threads.

Modified run() Method (prints once):

public void run() {
    System.out.println("Thread " + Thread.currentThread().getName() + " prints");
}

Main Method with Delays:

public static void main(String[] args) throws InterruptedException {
    ExecutorService cachedPool = Executors.newCachedThreadPool();
    cachedPool.submit(new TaskRunnable());
    Thread.sleep(1000);
    cachedPool.submit(new TaskRunnable());
    Thread.sleep(1000);
    cachedPool.submit(new TaskRunnable());
    Thread.sleep(1000);
    cachedPool.submit(new TaskRunnable());
    Thread.sleep(1000);
    cachedPool.submit(new TaskRunnable());
    // cachedPool.shutdown();
}

Result: Demonstrates thread reuse, as threads persist in the pool.

Example of newFixedThreadPool(int nThreads) Method

Main Method:

public static void main(String[] args) throws InterruptedException {
    ExecutorService fixedPool = Executors.newFixedThreadPool(3);
    fixedPool.submit(new TaskRunnable());
    fixedPool.submit(new TaskRunnable());
    fixedPool.submit(new TaskRunnable());
    fixedPool.submit(new TaskRunnable());
    fixedPool.submit(new TaskRunnable());
    // fixedPool.shutdown();
}

Result: Only three threads are created, matching the specified size.

Using ThreadPoolExecutor Class to Create a Custom Thread Pool

Full-Parameter Constructor of ThreadPoolExecutor:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

Parameter Descriptions:

  1. corePoolSize (Core Pool Size): When a task is submitted, if the number of created threads is less than corePoolSize, a new thread is created to execute the task, even if idle threads exist. Once the number reaches or exceeds corePoolSize, new threads are created only if no idle threads are available.
  2. maximumPoolSize (Maximum Pool Size): The maximum number of threads allowed in the pool. If the queue is full and the number of created threads is less than maximumPoolSize, new threads are created. Ignored for unbounded queues.
  3. keepAliveTime (Thread Keep-Alive Time): Idle threads exceeding this time are terminated if the number of threads exceeds corePoolSize. Can be applied to core threads via allowCoreThreadTimeOut(boolean).
  4. unit (Time Unit): Unit for keepAliveTime (e.g., TimeUnit.SECONDS).
  5. workQueue (Task Queue): A blocking queue for holding tasks. Interaction rules:
    • If threads < corePoolSize, new threads are created.
    • If threads >= corePoolSize, tasks are queued.
    • If queue is full and threads < maximumPoolSize, new threads are created.
    • Otherwise, tasks are rejected.
  6. threadFactory (Thread Factory): Creates new threads with uniform naming (e.g., pool-m-thread-n).
  7. handler (Rejection Handler): Handles tasks when the pool and queue are full. Options:
    • AbortPolicy (default): Throws RejectedExecutionException.
    • CallerRunsPolicy: Executes task in caller's thread.
    • DiscardPolicy: Silently discards task.
    • DiscardOldestPolicy: Discards oldest queued task and retries.

Conditions for Creating a Thread Pool:

  • corePoolSize >= 0
  • maximumPoolSize >= 1
  • maximumPoolSize >= corePoolSize
  • keepAliveTime >= 0
  • workQueue != null
  • threadFactory != null (default: DefaultThreadFactory)
  • handler != null (default: AbortPolicy)

Analogy: Think of the thread pool as a restaurant:

  • corePoolSize: Number of permanent staff (core threads).
  • maximumPoolSize: Maximum staff capacity (total threads).
  • keepAliveTime: Time before temporary staff are let go.
  • unit: Time unit for keepAliveTime.
  • workQueue: Waiting area for customers (tasks).
  • threadFactory: Hiring source for staff.
  • handler: Policy for turning away customers when full.

Example of Using ThreadPoolExecutor:

public class CustomThreadPool {
    ThreadPoolExecutor pool = new ThreadPoolExecutor(
        3,                     // Core pool size (>=0)
        6,                     // Maximum pool size (>=1, >= core size)
        60,                    // Keep-alive time
        TimeUnit.SECONDS,      // Time unit
        new ArrayBlockingQueue<>(3), // Task queue
        Executors.defaultThreadFactory(), // Thread factory
        new ThreadPoolExecutor.AbortPolicy() // Rejection handler
    );
}

Summary of Custom Thread Pools:

  1. Create an empty pool.
  2. Submit tasks; threads are created and returned after execution.

Critical Points with Continuous Task Submission:

  1. When core threads are full, tasks queue.
  2. When core threads and queue are full, temporary threads are created.
  3. When core threads, queue, and temporary threads are full, rejection policy triggers.

What Is the Optimal Thread Pool Size?

Understanding Maximum Parallelism: For example, a 4-core, 8-thread CPU has a maximum parallelism of 8.

Method to Get Maximum Parallelism:

public class Main {
    public static void main(String[] args) {
        int processorCount = Runtime.getRuntime().availableProcessors();
        System.out.println("Available processors: " + processorCount);
    }
}

Result: Outputs the number of available processors (e.g., 12).

Guidelines for Optimal Size:

1. CPU-Intensive Tasks: Use maximum parallelism + 1.

  • Note: +1 ensures a backup thread in case of failures.

2. I/O-Intensive Tasks: Use maximum parallelism * expected CPU utilization * (total time / CPU time).

  • Example: Reading two local files and summing them. Assume reading takes 1 second (waiting time) and summing takes 1 second (CPU time). Total time = 2 seconds, CPU time = 1 second. With maximum parallelism = 12 and expected CPU utilization = 100%:
    • Calculation: 12 * 100% * (2 / 1) = 24.

Thus, optimal thread pool size depends on task type and system resources.

Tags: java multithreading ThreadPoolExecutor executors Thread Pool

Posted on Tue, 14 Jul 2026 16:16:55 +0000 by php3ch0