Detailed Explanation of ThreadPoolExecutor in Java

Detailed Explanation of ThreadPoolExecutor in Java

Description
A thread pool is a form of multi-threading where tasks are added to a queue and then automatically started after threads are created. ThreadPoolExecutor is the most feature-rich and flexible thread pool implementation class in Java. Understanding its working principle is crucial for writing efficient concurrent programs.

Core Parameter Analysis
ThreadPoolExecutor has 7 core parameters. Let's start from the simplest ones:

  1. Core Pool Size (corePoolSize)

    • The number of threads that are kept alive in the thread pool (even when idle).
    • By default, core threads do not timeout and terminate unless allowCoreThreadTimeOut is set.
  2. Maximum Pool Size (maximumPoolSize)

    • The maximum number of threads allowed to be created in the thread pool.
    • When the work queue is full and all core threads are busy, new threads are created until this limit is reached.
  3. Work Queue (workQueue)

    • A blocking queue used to hold tasks waiting to be executed.
    • Common implementations: ArrayBlockingQueue (bounded), LinkedBlockingQueue (unbounded), SynchronousQueue (does not store).
  4. Keep Alive Time (keepAliveTime)

    • When the number of threads exceeds the core pool size, this is the maximum time that excess idle threads will wait for new tasks before terminating.
  5. Time Unit (unit)

    • The time unit for keepAliveTime (seconds, milliseconds, etc.).
  6. Thread Factory (threadFactory)

    • A factory for creating new threads. Can set thread names, priorities, etc.
  7. Rejected Execution Handler (handler)

    • The policy for handling new tasks when all threads are busy and the work queue is full.

Thread Pool Workflow
Let's understand the execution logic of a thread pool through a concrete example:

Assume thread pool configuration: corePoolSize=2, maximumPoolSize=5, workQueue capacity=10

  1. Task Submission Phase

    • Tasks 1-2: Create core threads for execution (Thread 1, Thread 2).
    • Tasks 3-12: Added to the work queue to wait for execution.
    • Task 13: The queue is now full, create a new thread (Thread 3).
    • Tasks 14-15: Continue creating new threads (Thread 4, Thread 5).
    • Task 16: Triggers the rejection policy.
  2. Task Processing Phase

    • After a core thread finishes its current task, it fetches the next task from the queue.
    • Non-core threads that are idle for longer than keepAliveTime will automatically terminate.

Rejected Execution Handler Details
Java provides 4 built-in rejection policies:

  1. AbortPolicy (Default)

    • Directly throws a RejectedExecutionException.
  2. CallerRunsPolicy

    • Makes the thread that submitted the task execute the task itself.
  3. DiscardPolicy

    • Silently discards the task without throwing an exception.
  4. DiscardOldestPolicy

    • Discards the oldest task in the queue, then retries executing the current task.

Practical Usage Example

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, // Core pool size
    5, // Maximum pool size
    60, // Keep-alive time
    TimeUnit.SECONDS, // Time unit
    new ArrayBlockingQueue<>(10), // Work queue
    Executors.defaultThreadFactory(), // Thread factory
    new ThreadPoolExecutor.AbortPolicy() // Rejected execution handler
);

// Submit tasks
for (int i = 0; i < 15; i++) {
    final int taskId = i;
    executor.execute(() -> {
        System.out.println("Task" + taskId + " executed in thread " +
            Thread.currentThread().getName());
    });
}

Thread Pool State Management
ThreadPoolExecutor uses an atomic integer to store state and thread count:

  • RUNNING: Accepts new tasks and processes queued tasks.
  • SHUTDOWN: Does not accept new tasks, but processes queued tasks.
  • STOP: Does not accept new tasks, does not process queued tasks, interrupts tasks in progress.
  • TIDYING: All tasks have terminated, worker count is zero.
  • TERMINATED: The terminated() method has completed execution.

Best Practice Recommendations

  1. Choose an appropriate queue based on task type:

    • CPU-intensive: Smaller queue size to avoid excessive task queuing.
    • IO-intensive: Larger queue to fully utilize CPU.
  2. Reasonably set the number of threads:

    • CPU-intensive: Number of cores + 1.
    • IO-intensive: 2 * Number of CPU cores.
  3. Use bounded queues to avoid memory overflow.

  4. Set meaningful names for threads to facilitate troubleshooting.

Understanding the operational mechanism of ThreadPoolExecutor can help you configure thread pool parameters reasonably according to specific business requirements in actual development, achieving optimal performance.