Thread Pool in Java
Java has built-in thread pools in
java.util.concurrent. Use
ExecutorService instead of creating a new
Thread per task — workers are reused from a pool. See
What is Thread Pool?.
Manual threads vs thread pool
Without a pool you create each worker yourself — e.g. four separate
Thread objects — and assign tasks to them by hand. That
works for a tiny, fixed workload but breaks down quickly.
Thread pool (newFixedThreadPool(4)) |
Four separate threads (new Thread(...)) |
|---|---|
Submit 10 tasks → 4 workers reuse themselves; extra tasks wait in
queue automatically.
|
Only 4 threads exist; each needs manual start() /
join(). No shared queue.
|
Issues with four separate threads
a. Threads die after join(). A pool keeps workers alive and pulls the
next job from the queue.
b. No central shutdown — pool: shutdown() / awaitTermination() once.
Manual: track every thread, join or interrupt each, handle partial
failure.
c. Hard to cap concurrency — pool size = 4 enforces the limit. With
manual threads, a bug that calls start() on extra threads bypasses the
cap.
d. More tasks than threads — 10 tasks, 4 threads: pool queues 6
automatically. Manual approach requires batching, another queue, or
creating 6 more threads.
1. Fixed thread pool — newFixedThreadPool
Fixed number of worker threads. Extra tasks wait in an unbounded queue.
Prefer this over creating Thread t1…t4 by hand.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedPoolExample {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
int taskId = i;
pool.submit(() ->
System.out.println("task " + taskId + " on " + Thread.currentThread().getName())
);
}
pool.shutdown(); // no new tasks; finish existing ones
}
}
// Example output (order varies):
// task 0 on pool-1-thread-1
// task 1 on pool-1-thread-2
// task 4 on pool-1-thread-1 ← thread-1 reused after task 0 finished
// task 2 on pool-1-thread-3
// ...
2. Cached thread pool — newCachedThreadPool
Creates threads as needed, reuses idle threads, removes threads idle for 60 seconds. Good for many short-lived tasks; unbounded growth possible.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedPoolExample {
public static void main(String[] args) {
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(() -> System.out.println("short task"));
pool.shutdown();
}
}
3. Custom pool — ThreadPoolExecutor
Full control over core/max pool size, queue type, and rejection policy.
import java.util.concurrent.*;
public class CustomPoolExample {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // core pool size
4, // max pool size
60L, TimeUnit.SECONDS, // idle thread timeout
new ArrayBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy()
);
pool.submit(() -> System.out.println("custom pool task"));
pool.shutdown();
pool.awaitTermination(1, TimeUnit.MINUTES);
}
}
4. Virtual thread pool (Java 21+)
Not an OS thread pool — each task gets a lightweight virtual thread. Same submit/wait API, far higher concurrency for I/O-bound work.
import java.util.concurrent.Executors;
public class VirtualPoolExample {
public static void main(String[] args) {
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10_000; i++) {
int id = i;
pool.submit(() -> System.out.println("vt-task " + id));
}
}
}
}