Create a Custom Thread Pool in Java
This is a learning-oriented implementation to understand worker threads, queueing, and task execution flow.
Real-World Use Cases
- controlled async task execution
- bounded concurrency for expensive operations
- internal job runners
Minimal Learning Implementation
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class SimpleThreadPool {
private final BlockingQueue<Runnable> queue;
public SimpleThreadPool(int workerCount, int capacity) {
this.queue = new LinkedBlockingQueue<>(capacity);
for (int i = 0; i < workerCount; i++) {
Thread worker = new Thread(this::runWorker, "worker-" + i);
worker.start();
}
}
public void execute(Runnable task) throws InterruptedException {
queue.put(task);
}
private void runWorker() {
while (true) {
try {
Runnable task = queue.take();
task.run();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
}
Java 8 vs JDK 11 vs Java 17 vs Java 21+
- Java 8: fixed pools are common and effective.
- JDK 11: no API shift for
ThreadPoolExecutor; same tuning model applies. - Java 17: still same executor model, but this is a common LTS baseline for modern Spring Boot services.
- Java 21+: for IO-heavy workloads, virtual-thread-per-task executors can reduce tuning overhead.
Production API Equivalent (ThreadPoolExecutor)
import java.util.concurrent.*;
public class PoolConfig {
private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(
4, // core
8, // max
60, TimeUnit.SECONDS, // keep-alive
new ArrayBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy()
);
public static void submitTask(Runnable task) {
EXECUTOR.execute(task);
}
}
For Java 21+ IO-heavy workloads:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {/* blocking IO */});
}
Java 25 Note
Thread pool fundamentals stay relevant. The improvement focus is observability, rejection policies, and workload isolation.
Key Takeaways
- Use custom pools only for learning.
- In production prefer
ThreadPoolExecutoror virtual-thread executors. - Always define queue capacity and rejection behavior.