ExecutorService

⭐ Interview Importance: MEDIUM
⏱️ Revision Time: 5 min

TL;DR

  • ExecutorService is an interface that provides a higher-level abstraction for managing and executing threads.
  • Instead of manually typing new Thread(task).start(), you submit tasks to an ExecutorService, which handles thread creation, recycling, and lifecycle management.
  • It is the core framework for Thread Pools in modern Java.

Concept

Manually creating threads is bad practice for production applications:

  1. Creating and destroying a thread object is a heavy OS operation.
  2. If you create a new thread for every incoming HTTP request, a spike of 10,000 requests will spawn 10,000 threads, crashing the server due to OutOfMemory errors.

ExecutorService solves this by decoupling “Task Submission” from “Task Execution”. You configure an ExecutorService (e.g., “Keep exactly 10 reusable threads alive”). You then .submit() thousands of Runnable or Callable tasks to it. The service places them in a queue, and the 10 worker threads continuously pull tasks from the queue, execute them, and grab the next one.

Examples

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

public class ExecutorServiceExample {
    public static void main(String[] args) {
        
        // Create an ExecutorService with a pool of exactly 3 threads
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // Submit 10 tasks to the service
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                String threadName = Thread.currentThread().getName();
                System.out.println("Executing Task " + taskId + " on " + threadName);
            });
        }
        
        // IMPORTANT: You must shut down the executor, otherwise your Java 
        // application will never terminate!
        executor.shutdown();
    }
}

Interview Questions

Q: What is the difference between submit() and execute()?
A: - execute(Runnable) takes a Runnable and returns void. If an unhandled exception occurs inside the task, it prints to the console and the specific worker thread might die (though the pool will replace it).

  • submit(Runnable/Callable) returns a Future object. If an exception occurs, it is swallowed and stored inside the Future. It will only be thrown when you call future.get().

Q: What is the difference between shutdown() and shutdownNow()?
A: - shutdown() is a graceful exit. It stops accepting new tasks, but allows all currently executing tasks AND tasks sitting in the waiting queue to finish before terminating the threads.

  • shutdownNow() is an aggressive exit. It stops accepting new tasks, returns a list of all tasks that were in the waiting queue (never executed), and attempts to interrupt the currently running tasks (using Thread.interrupt()).