ExecutorService

Why Thread Pools

Creating a new platform thread for every task is expensive (about 1 MB stack, OS call). An ExecutorService keeps a pool of reusable threads and a queue of tasks.

submit(task) ─► ┌──────────────┐     ┌──────────┐
submit(task) ─► │  task queue  │ ──► │ thread 1 │
submit(task) ─► └──────────────┘ ──► │ thread 2 │
                                 ──► │ thread 3 │
                                     └──────────┘
Factory methodPool
Executors.newFixedThreadPool(n)n threads, unbounded queue — CPU-bound work
Executors.newCachedThreadPool()grows as needed, idle threads die after 60 s
Executors.newSingleThreadExecutor()one thread, tasks run in order
Executors.newScheduledThreadPool(n)delayed and periodic tasks
Executors.newVirtualThreadPerTaskExecutor()Java 21: one virtual thread per task (I/O-bound)
new ThreadPoolExecutor(core, max, keepAlive, unit, queue, handler)full control (bounded queue, rejection policy)

Submitting Tasks

Example

A single-thread executor runs tasks in submission order. ExecutorService is AutoCloseable since Java 19: try-with-resources calls close(), which waits for all tasks.

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        try (ExecutorService mailer = Executors.newSingleThreadExecutor()) {
            for (String user : new String[]{"ravi", "anita", "john"}) {
                mailer.submit(() -> System.out.println("Welcome mail sent to " + user));
            }
        } // close(): no new tasks, wait for queued tasks to finish
        System.out.println("All mails sent");
    }
}

Output

Welcome mail sent to ravi
Welcome mail sent to anita
Welcome mail sent to john
All mails sent

Shutting Down

MethodEffect
shutdown()stop accepting tasks; queued tasks still run
shutdownNow()interrupt running tasks, return queued ones
awaitTermination(t, unit)block until finished or timeout
close() (Java 19)shutdown + wait; use with try-with-resources

Note: A pool that is never shut down keeps the JVM alive (its threads are non-daemon). In Spring Boot, let the framework manage pools (ThreadPoolTaskExecutor bean, @Async).