Thread Pool
Keep a fixed set of worker threads that pull tasks from a shared queue, so thread-creation cost is paid once and concurrency stays bounded, instead of spawning one new thread per task.
The problem
ImageUploadHandler.onUpload() spins up a brand-new thread for every incoming image, so
thumbnail generation never blocks the request. It works beautifully for ten uploads and
catastrophically for ten thousand: each thread costs real memory and OS scheduling overhead
to create, and nothing here puts a ceiling on how many can exist at once.
The system does not get slower because the work got harder - it gets slower, and eventually falls over, because of the overhead of constantly creating and tearing down the threads doing the work.
The solution
Create a fixed number of worker threads exactly once, when the pool starts up. Each worker loops forever: pull the next task off a shared queue, run it, loop back for the next one. Nothing about that loop involves creating or destroying a thread mid-flight.
Callers never touch a thread directly - they call submit(task), which drops the task on
the queue and returns immediately. However many tasks arrive, however fast, the number of
workers processing them never changes; the queue is what absorbs the difference between
arrival rate and processing rate.
- 1A request arrives that needs a thumbnail generated. The handler does not spawn a thread for it.
- 2The pool never runs the task itself - it drops it on the shared queue and returns immediately.
- 3One of a fixed four workers, currently idle, pulls the next task off the queue - blocking cheaply while the queue was empty.
- 4The worker actually resizes the image. This can take a while; the other three workers are unaffected.
- 5A hundred more uploads arrive in the same second. No new threads spawn - tasks simply queue up.
- 6The queue absorbs the burst. Exactly four workers process it, at whatever pace four workers actually manage.
- 7The moment this worker finishes task #1, it loops back and pulls the next one - the thread itself is never destroyed between tasks.
Structure
ThreadPool has both the workers and the queue, but callers only ever call submit() on
the pool - nothing outside this diagram touches Worker or TaskQueue directly.
Code
Same example three ways: bounded thumbnail generation behind a fixed worker pool.
When to use it
- Work arrives in short-lived units (a request, a job, a task) at a rate that can burst well past what should ever run simultaneously.
- Thread creation cost (or the cost of any other expensive-to-create worker) is measurably hurting throughput, and the work itself does not require an unbounded number of concurrent workers to make progress.
Pitfalls
- Wrong sizing for the workload. CPU-bound tasks want a pool close to the core count; I/O-bound tasks (mostly waiting on network or disk) tolerate, and often need, a much larger one. One "correct" pool size does not exist across workload types.
- Unbounded queue hiding overload. A queue with no size limit turns "too much work" into "memory grows until the process dies" instead of a visible, handleable backpressure signal.
- Blocking a worker forever. A task that never returns (a stuck network call with no timeout) permanently removes one worker from the pool. With a small pool, a handful of stuck tasks can stall everything behind them.
Don't confuse it with
- Producer-Consumer. A thread pool's workers are consumers pulling off a queue that callers produce onto - Thread Pool is a specific, bounded instance of the broader Producer-Consumer relationship, not a different pattern competing with it.
- Flyweight. Both save cost through reuse instead of repeated creation, but Flyweight shares immutable data across many logical objects; a pooled thread carries no shared state between the unrelated tasks it happens to run one after another.
- Async/await without a pool. Cooperative concurrency on a single thread (an event loop) solves a related problem - many pending operations without many OS threads - through an entirely different mechanism, not a fixed set of worker threads.
Check yourself
A thousand upload requests arrive in one second. What does a correctly sized thread pool do?