Fundamentals 12 min read

Mastering Java Thread Pools: Fixed vs. Cached Executors Explained

This article explains Java thread pools, their performance benefits, and how to create and use FixedThreadPool and CachedThreadPool via the Executors utility, providing code examples, execution results, and guidance on selecting the appropriate pool for different concurrency scenarios.

FunTester
FunTester
FunTester
Mastering Java Thread Pools: Fixed vs. Cached Executors Explained

What is a Java Thread Pool?

Java thread pools are a concurrency mechanism that reuses a set of pre‑created threads to execute tasks, reducing the overhead of thread creation and destruction and improving resource utilization, especially in high‑load performance testing.

Creating Thread Pools with Executors

The java.util.concurrent.Executors class offers factory methods for common pool types. In performance testing, the two most used pools are FixedThreadPool and CachedThreadPool.

1. FixedThreadPool

Method signature:

public static ExecutorService newFixedThreadPool(int nThreads)

This creates a ThreadPoolExecutor with a fixed number of threads, an unbounded task queue, and no core‑thread timeout. The pool size is limited to nThreads; if a thread dies, a new one is created when needed. Shutdown is performed via ExecutorService#shutdown().

Example usage:

ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 4; i++) {
    executorService.execute(() -> {
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(System.currentTimeMillis() + "  Hello FunTester!  " + Thread.currentThread().getName());
    });
}
executorService.shutdown();

Running the program prints timestamps that show the first three tasks execute concurrently on three different threads, while the fourth task waits until one thread becomes free. FixedThreadPool is suitable when the maximum number of concurrent threads must be bounded.

2. CachedThreadPool

Method signature:

public static ExecutorService newCachedThreadPool()

This pool creates new threads as needed and reuses idle ones. Threads that stay idle for more than 60 seconds are terminated. Although the theoretical maximum is Integer.MAX_VALUE, actual limits are imposed by the OS and available resources.

Basic demo:

ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 8; i++) {
    executorService.execute(() -> {
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(System.currentTimeMillis() + "  Hello FunTester!  " + Thread.currentThread().getName());
    });
}
executorService.shutdown();

The output shows eight tasks starting almost simultaneously, each handled by a distinct thread, demonstrating that the pool can grow to match the workload.

Thread‑reuse demonstration (tasks submitted with staggered delays):

ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 8; i++) {
    Thread.sleep(i * 100);
    executorService.execute(() -> {
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(System.currentTimeMillis() + "  Hello FunTester!  " + Thread.currentThread().getName());
    });
}
executorService.shutdown();

Only two threads are created because each new task arrives after the previous one finishes, allowing the same thread to be reused.

Exception handling demo (tasks that throw RuntimeException):

ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 2; i++) {
    Thread.sleep(i * 150);
    executorService.execute(() -> {
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(System.currentTimeMillis() + "  Hello FunTester!  " + Thread.currentThread().getName());
        throw new RuntimeException("Thread exception");
    });
}
executorService.shutdown();

The console shows the exception stack trace for each thread, and the pool creates a new thread if a task’s thread terminates unexpectedly, a behavior shared by both fixed and cached pools.

When to Use Which Pool?

Use FixedThreadPool when you need a predictable upper bound on concurrent threads, such as limiting load on a database or external service. Use CachedThreadPool for short‑lived, bursty tasks where you want the pool to expand quickly and shrink when idle.

Both pools simplify multithreaded programming by hiding thread lifecycle management behind a simple task‑submission API.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaconcurrencyThreadPoolExecutorServicePerformanceTestingCachedThreadPoolFixedThreadPool
FunTester
Written by

FunTester

10k followers, 1k articles | completely useless

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.