The ThreadPoolExecutor is the core implementation behind Java’s thread pool mechanism.
All factory methods in Executors (newFixedThreadPool, newCachedThreadPool, etc.) internally use ThreadPoolExecutor.
ThreadPoolExecutor is a concrete class in java.util.concurrent that:
- Manages a pool of worker threads
- Accepts tasks (
Runnable/Callable) - Controls thread creation, reuse, and termination
- Uses a queue to hold waiting tasks
- Applies rejection policies when overloaded
It gives full control over how concurrency behaves.
Executor
↓
ExecutorService
↓
AbstractExecutorService
↓
ThreadPoolExecutor
This means:
- It supports task execution
- It supports lifecycle management
- It supports result handling via
Future
The Executors utility class provides convenience methods, but they:
- Hide important configuration
- Often create unbounded queues or threads
- Can lead to memory leaks and OOM errors
Example problem:
Executors.newFixedThreadPool(10);Internally uses:
- Unbounded
LinkedBlockingQueue→ tasks can pile up endlessly
ThreadPoolExecutor exists to give explicit control.
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)Every parameter directly affects performance and stability.
- Minimum number of threads kept alive
- Threads are created even if idle
- Tasks are executed immediately if core threads are available
Example:
corePoolSize = 4→ At least 4 threads always exist.
- Maximum number of threads allowed
- If number of threads == corePoolSize and queue is full, new threads are created up to this limit
Example:
maximumPoolSize = 8→ Pool can scale from 4 → 8 threads under load.
- Time an extra thread (beyond core) can stay idle
- After timeout, thread is terminated
keepAliveTime = 30 secondsApplies only to threads above corePoolSize (unless configured otherwise).
Defines unit for keepAliveTime:
TimeUnit.SECONDS
TimeUnit.MILLISECONDSHolds tasks waiting for execution.
Common implementations:
| Queue | Behavior |
|---|---|
ArrayBlockingQueue |
Bounded, fixed capacity |
LinkedBlockingQueue |
Optional bound (often unbounded) |
SynchronousQueue |
No storage, direct handoff |
PriorityBlockingQueue |
Priority-based execution |
- Large queue → fewer threads, more waiting
- Small queue → more threads, faster execution
- Unbounded queue → risk of OOM
Responsible for creating threads.
Used to:
- Name threads
- Set daemon status
- Set priority
- Attach uncaught exception handlers
Example:
ThreadFactory factory = r -> {
Thread t = new Thread(r);
t.setName("worker-thread");
t.setDaemon(false);
return t;
};Triggered when:
- Queue is full
- Max threads are active
new ThreadPoolExecutor.AbortPolicy()- Throws
RejectedExecutionException - Safest for detecting overload
new ThreadPoolExecutor.CallerRunsPolicy()- Task runs in the calling thread
- Slows down producer
- Acts as backpressure mechanism
new ThreadPoolExecutor.DiscardPolicy()- Silently drops task
- Dangerous for critical systems
new ThreadPoolExecutor.DiscardOldestPolicy()- Drops oldest queued task
- Enqueues new task
When a task is submitted:
1. If active threads < corePoolSize
→ Create new thread
2. Else if queue not full
→ Enqueue task
3. Else if active threads < maximumPoolSize
→ Create new thread
4. Else
→ Reject task (handler invoked)
This flow is core to understanding thread pools.
ExecutorService executor =
new ThreadPoolExecutor(
2,
4,
30,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);Meaning:
- Minimum 2 threads
- Maximum 4 threads
- Queue size = 5
- Reject tasks when overloaded
Example:
core = 2
max = 4
queue = 3Task arrival:
| Task | Action |
|---|---|
| 1 | Create thread |
| 2 | Create thread |
| 3 | Queue |
| 4 | Queue |
| 5 | Queue |
| 6 | Create extra thread |
| 7 | Create extra thread |
| 8 | Reject |
By default:
- Core threads never die
You can allow core threads to time out:
executor.allowCoreThreadTimeOut(true);Now even core threads respect keepAliveTime.
Useful for:
- Bursty workloads
- Resource optimization
Important methods:
getPoolSize()
getActiveCount()
getQueue().size()
getCompletedTaskCount()Used in production monitoring.
executor.shutdown();- Stops accepting new tasks
- Finishes existing tasks
executor.shutdownNow();- Interrupts running tasks
- Returns queued tasks
| Executors | ThreadPoolExecutor |
|---|---|
| Simple | Advanced |
| Less control | Full control |
| Risky defaults | Explicit limits |
| Good for demos | Best for production |
- Avoid
Executors.newFixedThreadPoolin production - Always use bounded queues
- Always define rejection policy
- Monitor pool metrics
- Size pool based on workload
- Shutdown pools properly
- Unbounded queues → memory leak
- Cached thread pool misuse
- Blocking I/O in small pools
- Ignoring rejected tasks
- Forgetting shutdown
A configurable, production-grade implementation of a thread pool.
- Core → minimum threads
- Max → upper limit under load
When queue is full and max threads are active.
They hide configuration and may cause OOM.
Direct handoff; used in cached thread pools.
Slowing task producers using CallerRunsPolicy.
-
ThreadPoolExecutoris the backbone of Java concurrency -
Task execution follows a strict decision flow
-
Queue choice is as important as thread count
-
Rejection policies define overload behavior
-
Executors are wrappers; ThreadPoolExecutor is the real engine
-
Correct configuration prevents performance disasters