Inside Java’s ThreadPoolExecutor: How Executors Actually Work
This article dissects Java’s Executors utility, revealing how Executors.newFixedThreadPool creates a ThreadPoolExecutor, detailing constructor parameters, task execution flow, thread creation, queue handling, rejection policies, and worker lifecycle, giving readers a clear understanding of the internal mechanics behind Java’s thread pools.
In the previous JUC article we introduced how to create thread pools using Executors; this article dives into the internal implementation of Executors, focusing on ThreadPoolExecutor.
The most common usage is Executors.newFixedThreadPool(int), which creates a fixed-size pool. The source code is:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}ThreadPoolExecutor’s constructor assigns corePoolSize, maximumPoolSize, keepAliveTime, workQueue, threadFactory and handler, with validation checks.
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;
}Key parameters:
corePoolSize : number of core threads that stay alive.
maximumPoolSize : upper limit; when exceeded tasks are rejected.
workQueue : the waiting queue, typically a LinkedBlockingQueue.
keepAliveTime : idle time before non‑core threads are terminated.
threadFactory : creates new Thread instances.
handler : policy for rejected tasks (AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy, DiscardPolicy).
The execute(Runnable) method decides whether to create a new thread, queue the task, or reject it. Its core logic is:
public void execute(Runnable command) {
if (command == null) throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
} else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
}Helper methods addIfUnderCorePoolSize, addIfUnderMaximumPoolSize, and addThread create new worker threads when needed, updating poolSize and the workers set.
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w);
if (t != null) {
w.thread = t;
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize) largestPoolSize = nt;
}
return t;
}Each Worker runs a loop that fetches tasks via getTask() and executes them with runTask. The getTask method pulls tasks from the queue, respecting shutdown state and keep‑alive time.
Runnable getTask() {
for (;;) {
int state = runState;
if (state > SHUTDOWN) return null;
Runnable r;
if (state == SHUTDOWN)
r = workQueue.poll();
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
r = workQueue.take();
if (r != null) return r;
// additional termination checks omitted for brevity
}
}The runTask method invokes beforeExecute, runs the task, then calls afterExecute and updates the completed task count.
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
beforeExecute(Thread.currentThread(), task);
try {
task.run();
++completedTasks;
afterExecute(task, null);
} catch (RuntimeException ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}When a worker finishes, workerDone removes it from the workers set, decrements poolSize, and may trigger termination.
void workerDone(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
if (--poolSize == 0)
tryTerminate();
} finally {
mainLock.unlock();
}
}This walkthrough explains how ThreadPoolExecutor manages thread creation, task queuing, execution, and shutdown, providing a solid foundation for understanding ScheduledThreadPoolExecutor in the next 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.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
