Types of Executor Services in Java Concurrency

The ExecutorService interface builds on the Executor interface to manage thread pools and execute tasks asynchronously. Java provides several implementations via the Executors utility class, each suited for different use cases. Lets understand with examples, focusing on their purpose and behavior.


Hierarchy:

java.util.concurrent
├── Executor (Interface)
│   └── ExecutorService (Interface)
│       ├── AbstractExecutorService (Abstract Class)
│       │   ├── ThreadPoolExecutor (Class)
│       │   │   └── ScheduledThreadPoolExecutor (Class) [also implements ScheduledExecutorService]
│       │   └── ForkJoinPool (Class)
│       └── ScheduledExecutorService (Interface)
│           └── ScheduledThreadPoolExecutor (Class)

What is ExecutorService?

  • Definition: A subinterface of Executor that adds lifecycle management (e.g., shutdown) and task submission with results (e.g., Future).
  • Key Methods: execute(Runnable), submit(Callable), shutdown(), invokeAll().
  • Purpose: Simplifies thread management by pooling threads and queuing tasks.

Types of Executor Services (via Executors Factory Methods)

The Executors class provides factory methods to create different ExecutorService implementations. Here are the main types:

1. newFixedThreadPool(int nThreads)

  • What It Is: Creates a thread pool with a fixed number of threads. Excess tasks are queued until a thread becomes available.
  • Behavior:
    • Maintains nThreads reusable threads.
    • Tasks wait in an unbounded queue (LinkedBlockingQueue) if all threads are busy.
  • Use Case: When you want to limit concurrency (e.g., server handling a fixed number of requests).
import java.util.concurrent.*;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2); // 2 threads

        for (int i = 0; i < 5; i++) {
            executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " executing");
                try { Thread.sleep(1000); } catch (Exception e) {}
            });
        }
        executor.shutdown();
    }
}

    Output (approximate):

    pool-1-thread-1 executing 
    pool-1-thread-2 executing (1s pause) 
    pool-1-thread-1 executing 
    pool-1-thread-2 executing (1s pause) 
    pool-1-thread-1 executing

    Note: Only 2 tasks run at a time; others queue.

    2. newCachedThreadPool()

    What It Is: Creates a thread pool that grows and shrinks dynamically based on demand.

    Behavior:

    Creates new threads as needed (up to Integer.MAX_VALUE).

    Idle threads are terminated after 60 seconds (configurable).

    Use Case: Short-lived, unpredictable tasks (e.g., handling bursty web requests).

    import java.util.concurrent.*;
    
    public class CachedThreadPoolExample {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newCachedThreadPool();
    
            for (int i = 0; i < 5; i++) {
                executor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " executing");
                    try { Thread.sleep(500); } catch (Exception e) {}
                });
            }
            executor.shutdown();
        }
    }

    Output (approximate):

    pool-1-thread-1 executing 
    pool-1-thread-2 executing 
    pool-1-thread-3 executing 
    pool-1-thread-4 executing 
    pool-1-thread-5 executing

    Note: All 5 tasks may run concurrently; threads are reused or created as needed.

    3. newSingleThreadExecutor()

    • What It Is: Creates a single-threaded executor that processes tasks sequentially.
    • Behavior:
      • Uses one thread with an unbounded queue (LinkedBlockingQueue).
      • Guarantees tasks execute one at a time in submission order.
    • Use Case: Sequential task execution (e.g., logging, background jobs).
    import java.util.concurrent.*;
    
    public class SingleThreadExecutorExample {
        public static void main(String[] args) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
    
            for (int i = 0; i < 3; i++) {
                executor.submit(() -> {
                    System.out.println(Thread.currentThread().getName() + " executing");
                    try { Thread.sleep(1000); } catch (Exception e) {}
                });
            }
            executor.shutdown();
        }
    }

      • Output:

      pool-1-thread-1 executing (1s pause)
      pool-1-thread-1 executing (1s pause) 
      pool-1-thread-1 executing</code>

      • Note: One thread handles all tasks sequentially.

      4. newScheduledThreadPool(int corePoolSize)

      • What It Is: Creates a thread pool that can schedule tasks to run after a delay or periodically.
      • Behavior:
        • Returns a ScheduledExecutorService (subinterface of ExecutorService).
        • Supports schedule(), scheduleAtFixedRate(), scheduleWithFixedDelay().
      • Use Case: Timed tasks (e.g., cron-like jobs, periodic updates).
      import java.util.concurrent.*;
      
      public class ScheduledThreadPoolExample {
          public static void main(String[] args) {
              ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
      
              // Schedule a task with 2s delay
              executor.schedule(() -> {
                  System.out.println("Task executed after delay");
              }, 2, TimeUnit.SECONDS);
      
              // Schedule a task every 1s after initial 0s delay
              executor.scheduleAtFixedRate(() -> {
                  System.out.println("Periodic task: " + System.currentTimeMillis());
              }, 0, 1, TimeUnit.SECONDS);
      
              // Let it run briefly, then stop
              try { Thread.sleep(5000); } catch (Exception e) {}
              executor.shutdown();
          }
      }

        • Output (approximate):

        Periodic task: 1698765432100 
        Periodic task: 1698765433100 Task executed after delay 
        Periodic task: 1698765434100 ...

        • Note: Combines fixed pool with scheduling capabilities.

        5. newWorkStealingPool() (Java 8+)

        • What It Is: Creates a work-stealing thread pool optimized for parallel tasks (uses ForkJoinPool).
        • Behavior:
          • Default parallelism = number of CPU cores (Runtime.getRuntime().availableProcessors()).
          • Threads “steal” tasks from others’ queues to balance load.
        • Use Case: CPU-intensive parallel tasks (e.g., divide-and-conquer algorithms).
        import java.util.concurrent.*;
        
        public class WorkStealingPoolExample {
            public static void main(String[] args) {
                ExecutorService executor = Executors.newWorkStealingPool();
        
                for (int i = 0; i < 4; i++) {
                    executor.submit(() -> {
                        System.out.println(Thread.currentThread().getName() + " executing");
                        try { Thread.sleep(1000); } catch (Exception e) {}
                    });
                }
                executor.shutdown();
            }
        }

          ForkJoinPool-1-worker-1 executing 
          ForkJoinPool-1-worker-2 executing 
          ForkJoinPool-1-worker-3 executing 
          ForkJoinPool-1-worker-4 executing

          • Note: Efficient for parallel workloads; thread count adapts to CPU.

          Comparison Table

          TypeThread CountTask QueueUse Case
          FixedThreadPoolFixed (n)UnboundedLimited concurrency
          CachedThreadPoolDynamic (0 to max)None (creates threads)Short, bursty tasks
          SingleThreadExecutor1UnboundedSequential tasks
          ScheduledThreadPoolFixed (n)Delayed/ScheduledTimed/periodic tasks
          WorkStealingPoolCPU cores (default)Work-stealingParallel, CPU-bound tasks

          Key Notes

          • Customization: Beyond Executors, use ThreadPoolExecutor directly for fine-tuned control (e.g., custom queue size, rejection policy).
          • Shutdown: Always call shutdown() or shutdownNow() to avoid resource leaks.
          • Callable vs. Runnable: submit() supports Callable (returns Future), while execute() takes Runnable (no return).

          Conclusion

          • FixedThreadPool: Controlled concurrency.
          • CachedThreadPool: Flexible, bursty workloads.
          • SingleThreadExecutor: One-at-a-time tasks.
          • ScheduledThreadPool: Timing is everything.
          • WorkStealingPool: Parallel power.

          Related Posts

          Leave a Reply

          Your email address will not be published. Required fields are marked *