Understanding Java ThreadPoolExecutor: States, Key Parameters, and Workflow
This article explains why thread pools are essential for efficient resource utilization, describes the five lifecycle states of a ThreadPoolExecutor, details its most important parameters such as core and maximum pool sizes, work queue, keep‑alive time and rejection policies, and walks through the complete task execution workflow.
Thread pools are used in many applications to reuse a limited number of expensive resources such as threads, similar to how connection pools reuse database connections. Creating and destroying threads incurs significant overhead, and modern multi‑core CPUs require multithreaded programs to achieve high performance.
Thread Pool States
A ThreadPoolExecutor transitions through five states from creation to termination, reflecting its internal execution situation.
State
Meaning
RUNNING
The pool accepts new tasks and processes tasks in the queue. Calling shutdown() moves it to SHUTDOWN; calling shutdownNow() moves it to STOP.
SHUTDOWN
No new tasks are accepted, but queued tasks continue to run. When the queue is empty and all workers have terminated, the pool moves to TIDYING.
STOP
New tasks are rejected, the queue is ignored, and currently executing tasks are interrupted. When all workers finish, the pool moves to TIDYING.
TIDYING
All tasks have completed and there are no active workers. After terminated() is invoked, the pool enters TERMINATED.
TERMINATED
The pool is fully shut down and all resources have been released.
Important Parameters
1. Thread State and Worker Count
The pool maintains a single AtomicInteger called ctl that encodes both the run state (high 3 bits) and the worker count (low 29 bits). This compact representation allows atomic updates of both values.
2. Core and Maximum Pool Size
corePoolSizedefines the number of core threads that stay alive (or idle) even when idle, while maximumPoolSize is the upper bound on the total number of threads that can be created.
When the number of active workers is below corePoolSize, new threads are created immediately. When it reaches corePoolSize, additional tasks are placed into the work queue.
3. Thread Factory
Thread creation is delegated to a ThreadFactory, allowing custom thread naming, priority, daemon status, etc.
4. Work Queue
Tasks that cannot be executed immediately are placed into a blocking queue. If the queue is unbounded, the pool will never create threads beyond corePoolSize. If the queue is bounded and becomes full, the pool may create temporary threads up to maximumPoolSize.
5. Keep‑Alive Time for Non‑Core Threads
When the queue is bounded and full, the pool creates temporary (non‑core) threads. These threads are terminated after being idle for keepAliveTime unless the pool is configured to allow core threads to time out.
6. Rejection Policy
If both the queue is full and the pool has reached maximumPoolSize, new tasks are handled by a RejectedExecutionHandler. The default policy throws a RejectedExecutionException, but alternatives include discarding the task, running it in the caller’s thread, or discarding the oldest queued task.
Workflow
The execution process can be divided into four main steps:
1. Task Submission
When a task is submitted, the pool may create a new worker, enqueue the task, or reject it, depending on the current worker count, core size, and queue capacity.
2. Worker Creation
Before creating a new worker, the pool checks that it is not shutting down, that the thread factory can create a thread, and that the current worker count is below the configured limits. If all checks pass, a Worker object is instantiated and the worker count is atomically incremented.
3. Starting the Worker
The newly created Worker starts its associated Thread via worker.thread.start(). The thread then executes the run() method of the Worker, which delegates to runWorker(this).
4. Fetching and Executing Tasks
The worker first runs the task it was created with (if any). Afterwards it repeatedly obtains tasks from the queue: workers above the core size use poll(keepAliveTime, unit) so they can time out and be reclaimed; workers at or below the core size use take() to wait indefinitely.
When a worker cannot obtain a task within its keep‑alive time, it terminates, reducing the pool’s worker count.
(End of article)
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
