Java > Concurrency and Multithreading > Executors and Thread Pools > Creating Executors and Thread Pools

Creating and Using a Fixed Thread Pool Executor

This example demonstrates how to create and use a fixed-size thread pool executor in Java. A fixed thread pool executor maintains a specific number of threads to execute tasks. If all threads are busy, new tasks are placed in a queue until a thread becomes available.

Core Concepts: Fixed Thread Pool Executor

A fixed thread pool uses a fixed number of threads. Tasks are submitted to the pool, and if all threads are currently busy, the tasks are placed in a queue. This approach is useful when you want to limit the number of concurrent operations to prevent resource exhaustion. Key classes involved are ExecutorService and Executors.

Code Example: Creating a Fixed Thread Pool

The code creates a fixed thread pool with 3 threads using Executors.newFixedThreadPool(3). It then submits 10 tasks to the executor. Each task prints a message indicating which thread is executing it and then sleeps for 1 second. Finally, the executor is shut down using executor.shutdown(), which prevents new tasks from being submitted but allows already submitted tasks to complete. The while loop waits for all tasks to finish.

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

public class FixedThreadPoolExample {

    public static void main(String[] args) {
        // Create a fixed thread pool with 3 threads
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // Submit tasks to the executor
        for (int i = 0; i < 10; i++) {
            final int taskNumber = i;
            executor.submit(() -> {
                System.out.println("Task " + taskNumber + " executed by " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000); // Simulate some work
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }

        // Shut down the executor
        executor.shutdown();

        // Wait for all tasks to complete
        while (!executor.isTerminated()) {
            // Wait until all threads are finished
        }

        System.out.println("All tasks finished");
    }
}

Real-Life Use Case

Fixed thread pools are suitable for scenarios where you need to limit the number of concurrent operations, such as processing incoming requests on a web server or handling a large number of file processing tasks. For example, you might want to restrict the number of concurrent database connections to prevent overloading the database server.

Best Practices

  • Always shut down the executor after submitting tasks using executor.shutdown() or executor.shutdownNow() to prevent resource leaks.
  • Handle InterruptedException properly when tasks are interrupted.
  • Choose the appropriate thread pool size based on the number of available CPU cores and the nature of the tasks (CPU-bound vs. I/O-bound).
  • Consider using a try-finally block to ensure that the executor is always shut down, even if an exception occurs.

Interview Tip

Explain the difference between shutdown() and shutdownNow(). shutdown() prevents new tasks from being submitted but allows already submitted tasks to complete, while shutdownNow() attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.

When to Use a Fixed Thread Pool

Use a fixed thread pool when you need to limit the number of concurrent tasks and want to ensure that the application doesn't consume excessive resources. It's a good choice for tasks that are relatively uniform in their execution time.

Memory Footprint

The memory footprint is determined by the number of threads in the pool and the memory required by each thread's stack. Increasing the pool size increases the memory consumption. Monitor the memory usage to avoid OutOfMemoryError exceptions.

Alternatives

Alternatives to a fixed thread pool include:

  • Cached thread pool: Creates new threads as needed, reusing previously constructed threads when they are available. Good for short-lived, asynchronous tasks.
  • Scheduled thread pool: Allows you to schedule tasks to run at a specific time or periodically.
  • ForkJoinPool: Designed for computationally intensive tasks that can be recursively broken down into smaller subtasks.

Pros

  • Limits the number of concurrent tasks, preventing resource exhaustion.
  • Provides predictable performance by controlling the number of active threads.

Cons

  • Tasks may be queued if all threads are busy, potentially increasing latency.
  • The thread pool size must be chosen carefully to balance resource utilization and performance.

FAQ

  • What happens if I submit more tasks than the thread pool size?

    If you submit more tasks than the thread pool size, the extra tasks will be placed in a queue until a thread becomes available to execute them.
  • How do I determine the optimal thread pool size?

    The optimal thread pool size depends on the nature of the tasks. For CPU-bound tasks, a thread pool size equal to the number of CPU cores is often a good starting point. For I/O-bound tasks, a larger thread pool size may be more appropriate.
  • What is the difference between shutdown() and shutdownNow()?

    shutdown() prevents new tasks from being submitted but allows already submitted tasks to complete, while shutdownNow() attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.