Chapter 17
Problem 13: Design a Thread Pool
The setup
"Implement a thread pool. Fixed number of worker threads. Accepts tasks (Runnables). Tasks queue if all workers are busy. Support shutdown."
Core design
class ThreadPool {
private final BlockingQueue taskQueue;
private final List workers;
private volatile boolean shutdown;
public ThreadPool(int poolSize) {
this.taskQueue = new LinkedBlockingQueue<>();
this.workers = new ArrayList<>(poolSize);
for (int i = 0; i < poolSize; i++) {
WorkerThread w = new WorkerThread();
workers.add(w);
w.start();
}
}
public void submit(Runnable task) {
if (shutdown) throw new RejectedExecutionException();
taskQueue.offer(task);
}
public void shutdown() {
shutdown = true;
workers.forEach(Thread::interrupt);
}
public void awaitTermination() throws InterruptedException {
for (WorkerThread w : workers) w.join();
}
private class WorkerThread extends Thread {
public void run() {
while (!shutdown || !taskQueue.isEmpty()) {
try {
Runnable task = taskQueue.poll(1, TimeUnit.SECONDS);
if (task != null) {
try { task.run(); }
catch (Throwable t) { /* log */ }
}
} catch (InterruptedException e) {
if (shutdown) break;
}
}
}
}
}
Design Decision: bounded vs unbounded queue
LinkedBlockingQueue (unbounded) means accepted tasks accumulate without limit — OOM risk under sustained overload. Better: bounded queue with a rejection policy.
// Bounded queue with rejection
this.taskQueue = new ArrayBlockingQueue<>(maxQueueSize);
public void submit(Runnable task) {
if (!taskQueue.offer(task)) {
// Rejection: caller-runs, drop, log, exception
rejectionPolicy.handle(task, this);
}
}
Java's ThreadPoolExecutor has these built in. For implementation interviews, demonstrate awareness.
Takeaway
Thread pools are a foundational concurrency primitive. The interview test is whether you understand: bounded queues prevent OOM; rejection policies handle overflow; worker threads must respect interrupts for clean shutdown; tasks should be Runnable / Callable (not coupled to specific code).