6.4 Executors & Thread Pools: Architecture, Sizing & Shutdown
← Back to Chapter 6: Multithreading & Concurrency
Manually allocating new Thread() in server applications wastes 1 MB of stack memory per thread and risks OutOfMemoryError: unable to create native thread. The Executor Framework manages reusable worker thread pools.
What is Thread Pool?
A Thread Pool is simply a collection of already-created threads that can execute tasks.
Suppose you have 4 threads but 10 tasks.
// first 4 tasks will get executed by 4 threads
Thread 1 → Task A
Thread 2 → Task B
Thread 3 → Task C
Thread 4 → Task D
// rest of the task will wait in the queue
Task E
Task F
Task G
When Thread 1 finishes Task A:
Thread 1 → Task E
So threads are reused instead of continuously creating new ones.
The remaining tasks wait in a queue.
Create thread → execute task → destroy thread ❌
Create threads once
↓
Execute task
↓
Reuse thread
↓
Execute another task
↓
Reuse again ✅
What is ExecutorService?
ExecutorService is a Java interface that provides a way to create and manage thread pools and submit tasks to them.
Thread Pool = Workers
ExecutorService = Manager
Why we need Thread Pool? Why can't we simply do new Thread()?
Imagine your application receives 1000 tasks. You could end up creating:
1000 tasks
↓
1000 threads
That's inefficient because threads consume memory and CPU resources. Therefore :
1000 tasks
↓
10 worker threads
↓
Tasks wait in queue
↓
Threads continuously process them
This is much better way.
Different common Thread Pools :
1. newFixedThreadPool()
Creates a fixed number of threads. Useful when you want controlled concurrency.
ExecutorService executor = Executors.newFixedThreadPool(5);
2. newCachedThreadPool()
Creates threads as needed and can reuse idle threads.Good for many short-lived asynchronous tasks, but you need to be careful because it can create many threads under heavy load.
ExecutorService executor = Executors.newCachedThreadPool();
3. newSingleThreadExecutor()
Only one worker thread. Tasks execute one after another.
ExecutorService executor = Executors.newSingleThreadExecutor();
How is this related to Spring Boot?
100 requests
↓
Controller
↓
Service
↓
Some slow operation
Instead of creating a new thread for every operation, you can configure a thread pool and let Spring manage it.
For example, with @Async:
@Async
public void sendEmail() {
// This can execute in a separate thread
}
Spring can use a configured executor/thread pool to execute these asynchronous tasks.
HTTP Request
↓
Spring Boot
↓
@Async method
↓
Executor
↓
Thread Pool
┌────┬────┬────┐
T1 T2 T3 T4
Thread Pool Sizing Formulas
- CPU-Intensive Tasks (Encryption, Video compression, Complex JSON parsing):
Pool Size = CPU Cores + 1
- I/O-Intensive Tasks (Database queries, REST API downstream calls, disk I/O):
Pool Size = CPU Cores * (1 + Wait Time / Compute Time)
4. Graceful Shutdown Protocol
executor.shutdown(); // Disable new task admissions
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow(); // Cancel executing tasks via interrupt
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
System.err.println("Thread pool failed to terminate");
}
}
} catch (InterruptedException ie) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}