Types of Thread Pools in Java

Naveen AnamaneniUpdated · 4 min read

The Executors class offers several factory methods, each creating a thread pool with a different behavior: how many threads it keeps, how it queues work, and when it creates new threads. Choosing the right one matters: the wrong pool can waste threads, run tasks out of order, or create so many threads that the program runs out of memory. In this lesson, you will learn each type of pool and when to use it.

Types of Thread Pools

Factory methodThreadsQueueBest for
newFixedThreadPool(n)Exactly n, kept aliveUnboundedSteady CPU work with a known limit
newCachedThreadPool()As many as needed; idle ones die after 60 sNone (hands tasks straight to a thread)Many short tasks with bursts
newSingleThreadExecutor()Exactly 1UnboundedTasks that must run one at a time, in order
newScheduledThreadPool(n)nDelay queueDelayed or repeating tasks
newWorkStealingPool()About one per coreOne deque per workerMany small, independent CPU tasks
newVirtualThreadPerTaskExecutor()A new virtual thread per task (Java 21)NoneMany tasks that mostly wait (I/O)

Example: Comparing Fixed, Single and Cached Pools

Let's see an example that submits six quick tasks to three kinds of pools and counts how many different threads each used.

PoolTypes.java▶ Try it
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class PoolTypes {
    static int threadsUsed(ExecutorService pool) {
        Set<String> names = ConcurrentHashMap.newKeySet();
        try (pool) {
            for (int i = 0; i < 6; i++) {
                pool.submit(() -> {
                    names.add(Thread.currentThread().getName());
                    sleep(100);
                });
            }
        }
        return names.size();
    }
 
    static List<Integer> orderOnSingleThread() {
        List<Integer> order = new ArrayList<>();
        try (ExecutorService single = Executors.newSingleThreadExecutor()) {
            for (int i = 1; i <= 5; i++) {
                int n = i;
                single.submit(() -> order.add(n));
            }
        }
        return order;
    }
 
    static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
 
    public static void main(String[] args) {
        System.out.println("Fixed pool of 2: "
            + threadsUsed(Executors.newFixedThreadPool(2)) + " threads");
        System.out.println("Single thread:   "
            + threadsUsed(Executors.newSingleThreadExecutor()) + " thread");
        System.out.println("Cached pool:     "
            + threadsUsed(Executors.newCachedThreadPool()) + " threads");
        System.out.println("Single-thread order: " + orderOnSingleThread());
    }
}
Output
Fixed pool of 2: 2 threads
Single thread:   1 thread
Cached pool:     6 threads
Single-thread order: [1, 2, 3, 4, 5]

Explanation: The fixed pool never uses more than its two threads; the other tasks queue up. The single-thread executor runs everything on one thread, in submission order, so the plain ArrayList is safe. The cached pool created a new thread for each task, because each one was busy sleeping when the next arrived.

Pools in Production

Common Mistakes

Using a cached pool for long-running tasks under heavy load. It can create thousands of threads.

Using a fixed pool sized far beyond the core count for CPU work. Extra threads only take turns.

Expecting a fixed pool with several threads to keep task order. Only the single-thread executor guarantees order.

Interview Corner

What types of thread pools does Executors provide?

newFixedThreadPool(n) keeps n threads with an unbounded queue; newCachedThreadPool() creates threads on demand, reuses idle ones and removes them after 60 seconds idle; newSingleThreadExecutor() runs tasks one at a time in submission order; newScheduledThreadPool(n) runs delayed and periodic tasks; newWorkStealingPool() is a ForkJoinPool with work stealing; and since Java 21, newVirtualThreadPerTaskExecutor() runs each task in a new virtual thread. Fixed and single pools have unbounded queues and cached pools unbounded threads, so production code often configures ThreadPoolExecutor explicitly.

Practice

  1. Easy: Submit ten tasks to newFixedThreadPool(4) and print which thread runs each.
  2. Harder: Create a ThreadPoolExecutor with 2 core threads, 4 maximum threads and a bounded queue of 2, submit 10 slow tasks, and observe the RejectedExecutionException.

Quick check: Types of thread pools

4 questions
1Which pool runs tasks one at a time, in order?
2Which pool creates new threads as needed?
3What risk does a fixed thread pool have under heavy load?
4Which executor is best for many tasks that mostly wait on I/O?

Key Takeaways

Was this tutorial helpful?

Lesson 412 of 482