Core Module
12 min forge

Java Thread Pools and Executors

Managing threads efficiently using the ExecutorService. Avoiding the "One Thread per Task" anti-pattern.

Java Thread Pools and Executors

πŸ›‘οΈ What are Executors?

The java.util.concurrent.ExecutorService is a high-level API for managing a pool of worker threads. Instead of manually creating new Thread() for every task (which is expensive and risks resource exhaustion), you submit tasks to a pool that manages thread reuse and scheduling.

⏰ When to Use

  • High Concurrency: When your application needs to handle many short-lived asynchronous tasks.
  • Resource Management: To limit the maximum number of concurrent threads to prevent memory crashes.
  • Async Execution: Running tasks in the background without blocking the main execution thread.

πŸ“Š Complexity & Performance

  • Initialization: Creating a thread pool has an $O(k)$ overhead where $k$ is the pool size.
  • Task Submission: Submitting a task is usually $O(1)$, though it may involve waiting on a blocking queue if the pool is full.
  • Context Switching: Thread pools reduce context switching overhead by reuse, but too many threads can still degrade performance due to CPU contention.

πŸ’» Code Example: Fixed Thread Pool

java Standard
import java.util.concurrent.*; public class ExecutorDemo { public static void main(String[] args) { // 1. Create a pool of 4 worker threads ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < 10; i++) { final int taskId = i; // 2. Submit task executor.submit(() -> { System.out.println("Processing task " + taskId + " on " + Thread.currentThread().getName()); try { Thread.sleep(500); } catch (InterruptedException e) {} }); } // 3. Graceful Shutdown executor.shutdown(); try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); } } }

⚠️ Interview Pitfalls

  1. Unbounded Queues: Executors.newFixedThreadPool() uses a LinkedBlockingQueue with no capacity limit. If tasks arrive faster than they are processed, the application will run out of memory (OutOfMemoryError).
  2. Infinite Waiting: Always call shutdown() and awaitTermination(). A thread pool that isn't shut down will prevent the JVM from exiting.
  3. Thread Visibility: Remember that variables shared between the main thread and executors must be volatile or accessed via thread-safe classes (like AtomicInteger).
  4. Exception Masking: In a thread pool, if a task throws an unchecked exception, it might be "swallowed" unless you are looking at the Future object returned by submit().