Thread Pools
TL;DR
- A Thread Pool is a collection of pre-instantiated, reusable threads managed by an
ExecutorService. - Thread pools eliminate the overhead of constantly creating and destroying threads.
- The
Executorsutility class provides factory methods to create various types of thread pools.
Concept
Under the hood, most thread pools are implemented via the ThreadPoolExecutor class. It manages a core pool size, a maximum pool size, a keep-alive time for idle threads, and a BlockingQueue where pending tasks wait.
Java provides a utility class called Executors with static factory methods to quickly spawn pre-configured pools.
Common Thread Pools:
- FixedThreadPool: A pool with a fixed number of threads. If all threads are busy, new tasks wait in an unbounded queue.
- CachedThreadPool: A pool that creates new threads as needed, but reuses previously constructed threads when they become available. Idle threads are killed after 60 seconds. Good for many short-lived tasks.
- SingleThreadExecutor: A pool with exactly one thread. Guarantees that tasks execute sequentially.
- ScheduledThreadPool: A pool designed to execute tasks after a delay, or periodically.
Examples
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExample {
public static void main(String[] args) {
// 1. Fixed Thread Pool (Good for CPU intensive / predictable loads)
ExecutorService fixedPool = Executors.newFixedThreadPool(4);
// 2. Cached Thread Pool (Good for massive amounts of quick I/O tasks)
ExecutorService cachedPool = Executors.newCachedThreadPool();
// 3. Single Thread Executor (Good for sequential queue processing)
ExecutorService singlePool = Executors.newSingleThreadExecutor();
// 4. Scheduled Thread Pool (Good for cron jobs / timeouts)
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(2);
scheduledPool.scheduleAtFixedRate(() -> {
System.out.println("Ping!");
}, 1, 3, TimeUnit.SECONDS); // Delay 1s, repeat every 3s
}
}
Interview Questions
Q: Why does Alibaba/SonarLint warn against using Executors.newFixedThreadPool() in production?
A: If you look at the source code for Executors.newFixedThreadPool(), it utilizes an LinkedBlockingQueue with an unbounded capacity (Integer.MAX_VALUE). If your server receives 10 million requests suddenly, the thread pool will happily queue all 10 million tasks in memory, causing a catastrophic OutOfMemoryError (OOM).
In production, it is highly recommended to manually instantiate a ThreadPoolExecutor and pass in a bounded queue (e.g., ArrayBlockingQueue(1000)) so the system rejects excess tasks instead of crashing the server.
Q: How do you decide the correct Thread Pool size?
A: - CPU-Bound tasks (encryption, math): Pool size should be CPU_CORES or CPU_CORES + 1. Having more threads than cores just wastes time on context switching.
- I/O-Bound tasks (database queries, HTTP calls): Pool size can be much larger (e.g., 50-200), because threads spend 99% of their time in the
WAITINGstate, not consuming CPU cycles.