Executors & Thread Pools — Java Interview Guide | Cracked Java
Senior

Executors & Thread Pools

The Executor framework, every ThreadPoolExecutor parameter, why the Executors factory methods are dangerous in production, pool sizing, rejection policies, and graceful shutdown.

Prereqs: thread-fundamentals

A thread pool reuses a fixed set of worker threads to run a stream of tasks, decoupling what you submit from how it runs. Creating a thread per request costs ~1 MB of stack each and thousands of context switches; a pool caps that cost, bounds concurrency, and gives you queueing, lifecycle control, and back-pressure. In Java this is the java.util.concurrent Executor framework, and at its heart sits one configurable class: ThreadPoolExecutor.

The Executor framework

Executor is a single method — execute(Runnable). ExecutorService adds lifecycle (shutdown, awaitTermination) and result-bearing submission (submit returning a Future, invokeAll, invokeAny). ScheduledExecutorService adds delayed and periodic execution. You program to these interfaces and let the concrete pool decide threading policy — the same separation-of-concerns that lets you swap a fixed pool for virtual threads in one line.

How a ThreadPoolExecutor runs a task

The behavior surprises people. Tasks do not spread across maximumPoolSize threads eagerly. The algorithm is: (1) if fewer than corePoolSize threads exist, start a new core thread; (2) otherwise try to enqueue the task; (3) only if the queue is full create threads up to maximumPoolSize; (4) if that also fails, invoke the rejection handler.

submit(task)
 |
 v
core threads full? --no--> start new core thread
 | yes
 v
queue.offer(task) ok? --yes--> task waits in queue
 | no (queue full)
 v
pool < maximumPoolSize? --yes--> start new (non-core) thread
 | no
 v
RejectedExecutionHandler
ThreadPoolExecutor task-admission order

The corollary: with an unbounded queue (LinkedBlockingQueue with no capacity), step 2 never fails, so maximumPoolSize is dead config and threads never grow past core. That is exactly the trap in Executors.newFixedThreadPool.

Configuration and lifecycle

The seven-arg constructor exposes corePoolSize, maximumPoolSize, keepAliveTime, the BlockingQueue<Runnable> (direct-handoff, bounded, or unbounded), a ThreadFactory (name your threads — your future self reading a thread dump will thank you), and a RejectedExecutionHandler.

Shut a pool down deliberately: shutdown() stops accepting work but drains the queue; shutdownNow() interrupts running tasks and abandons the queue. Either way, no pool is closed until you awaitTermination.

Questions

7 in this topic