Blog Concurrency and Multithreading

Executors.newCachedThreadPool() vs Executors.newFixedThreadPool()

Java’s java.util.concurrent package provides a robust framework for managing threads. Two commonly used thread pools from the Executors class are newCachedThreadPool() and newFixedThreadPool(int nThreads). In this blog, we will delve into the differences, use cases, examples, and best practices for these thread pools.


Overview

1. newCachedThreadPool()

  • Characteristics:
    • Creates a thread pool with potentially unlimited threads.
    • Threads are created as needed and reused when available.
    • Idle threads are terminated after 60 seconds.
    • Uses a SynchronousQueue, meaning no task queueing; tasks are handed directly to threads.
  • When to Use:
    • For many short-lived asynchronous tasks.
    • When you want minimal overhead for idle threads.
    • In scenarios where the workload is bursty and unpredictable.
  • Risks:
    • Unbounded thread creation can lead to resource exhaustion under heavy workloads.
    • Not ideal for long-running tasks or controlled concurrency.

2. newFixedThreadPool(int nThreads)

  • Characteristics:
    • Creates a thread pool with a fixed number of threads (nThreads).
    • Excess tasks are queued in an unbounded LinkedBlockingQueue until threads are available.
    • Threads remain alive indefinitely unless the pool is explicitly shut down.
  • When to Use:
    • For stable workloads with predictable concurrency requirements.
    • For long-running tasks or controlled resource usage.
  • Risks:
    • An unbounded task queue can grow excessively, causing OutOfMemoryError.

Key Differences

FeaturenewCachedThreadPoolnewFixedThreadPool
Thread CountDynamically adjusts (0 to unbounded).Fixed (nThreads).
Idle Thread TimeTerminates after 60 seconds of inactivity.Threads live indefinitely.
QueueingNo queueing (uses SynchronousQueue).Tasks are queued if all threads are busy.
Use CaseShort-lived bursty tasks.Long-running predictable tasks.
RiskUnbounded threads can overwhelm resources.Unbounded queue can exhaust memory.

Examples

1. Example: Using newCachedThreadPool()

This example demonstrates handling short-lived tasks with newCachedThreadPool().

Java
package com.codinginterviewprep.corejava.multithreading.executors;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class TestCachedThreadPoolExample {

    // ANSI color codes
    private static final String[] COLORS = {
            "\u001B[31m", // Red
            "\u001B[32m", // Green
            "\u001B[33m", // Yellow
            "\u001B[34m", // Blue
            "\u001B[35m", // Purple
            "\u001B[36m", // Cyan
            "\u001B[37m", // White
            "\u001B[91m", // Bright Red
            "\u001B[92m", // Bright Green
            "\u001B[93m"  // Bright Yellow
    };
    private static final String RESET = "\u001B[0m"; // Reset color

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                String threadName = Thread.currentThread().getName();
                int threadId = threadName.hashCode() % COLORS.length; // Generate a thread ID based on hash
                if (threadId < 0) threadId += COLORS.length; // Handle negative hash values
                String color = COLORS[threadId]; // Assign a color based on thread ID
                System.out.println(color + threadName + " is executing a task" + RESET);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException exception) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        executorService.shutdown();
    }
}
  • Output: Threads are created dynamically and reused for subsequent tasks.

2. Example: Using newFixedThreadPool()

This example demonstrates handling a fixed number of threads with newFixedThreadPool().

Java
package com.codinginterviewprep.corejava.multithreading.executors;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestFixedThreadPoolExample {

    // ANSI color codes
    private static final String[] COLORS = {
            "\u001B[31m", // Red
            "\u001B[32m", // Green
            "\u001B[33m"  // Yellow
    };
    private static final String RESET = "\u001B[0m"; // Reset color

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            executorService.execute(() -> {
                String threadName = Thread.currentThread().getName();
                // Extract thread index (1-based) from the thread name, e.g., pool-1-thread-X
                int threadId = Integer.parseInt(threadName.split("-")[3]) - 1; // Adjust to 0-based index
                String color = COLORS[threadId % COLORS.length]; // Assign color based on thread index
                System.out.println(color + threadName + " is executing a task" + RESET);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException interruptedException) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        executorService.shutdown();
    }
}
  • Output: Only 3 threads execute tasks simultaneously, and the rest are queued.

Use Cases

newCachedThreadPool()

  • Advantages:
    • Ideal for short-lived, bursty tasks.
    • Minimizes overhead by terminating idle threads.
  • Example Scenarios:
    • Processing lightweight HTTP requests.
    • Asynchronous logging.
    • Parallel computations with variable load.

newFixedThreadPool()

  • Advantages:
    • Predictable resource usage.
    • Suitable for long-running tasks or stable workloads.
  • Example Scenarios:
    • Batch processing (e.g., image processing).
    • Background jobs with known concurrency limits.
    • Server handling a fixed number of simultaneous connections.

Best Practices

  1. Monitor Resource Usage:
    • Use tools like JMX or external monitoring to ensure threads and memory are within safe limits.
  2. Bound the Queue:
    • For newFixedThreadPool, consider wrapping it with a ThreadPoolExecutor and using a bounded queue to prevent OutOfMemoryError.
  3. Graceful Shutdown:
    • Always call shutdown() or shutdownNow() to release resources after use.
  4. Rejection Policies:
    • Use a custom ThreadPoolExecutor to define how tasks should be handled when the pool is full (e.g., CallerRunsPolicy).
  5. Thread Safety:
    • Ensure tasks are thread-safe to avoid race conditions.

Custom ThreadPoolExecutor Example

For advanced control, use ThreadPoolExecutor directly:

Java
import java.util.concurrent.*;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = new ThreadPoolExecutor(
            2,                  // Core pool size
            5,                  // Maximum pool size
            1, TimeUnit.MINUTES,// Keep-alive time
            new ArrayBlockingQueue<>(10), // Task queue
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy() // Rejection policy
        );

        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " is executing a task");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        executor.shutdown();
    }
}
  • Features:
    • Bounded queue to limit task backlog.
    • Custom rejection policy to handle excess tasks.

Conclusion

Choosing between newCachedThreadPool() and newFixedThreadPool() depends on your application’s workload and concurrency requirements. Use newCachedThreadPool() for short-lived, bursty tasks and newFixedThreadPool() for controlled, long-running tasks. For advanced scenarios, leverage ThreadPoolExecutor for fine-grained control over thread pool behavior.

By understanding these options, you can design efficient and robust multithreaded applications.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over