Concurrency

54. ExecutorService

ExecutorService decouples task submission from thread management; always use shutdown()+awaitTermination() or try-with-resources (Java 19+) to drain the queue gracefully, and prefer submit() over execute() to capture exceptions.

Java 21 (core language)

Explanation

ExecutorService manages a pool of threads. Executors factory methods: newFixedThreadPool(n) (n reusable threads), newCachedThreadPool() (unbounded, spawns on demand), newSingleThreadExecutor() (single sequential thread), newScheduledThreadPool(n) (delayed/periodic tasks).

execute(Runnable) submits a task with no return value and no way to detect exceptions (they go to the thread's uncaught exception handler). submit(Callable<T>) returns a Future<T> โ€” exceptions are stored and rethrown when you call future.get().

Shutdown: shutdown() stops accepting new tasks but finishes queued tasks; shutdownNow() interrupts running tasks and returns unstarted ones. Always call shutdown() โ€” a pool that's never shut down keeps its threads alive and prevents JVM exit. Since Java 19, ExecutorService implements AutoCloseable โ€” use try-with-resources for convenience.

Example

import java.util.List;
import java.util.concurrent.*;

public class ExecutorServiceDemo {
    static String slowTask(int id) throws InterruptedException {
        Thread.sleep(50);
        return "Result from task " + id;
    }

    public static void main(String[] args) throws Exception {
        // Fixed thread pool โ€” try-with-resources (Java 19+, closes=shutdown+await)
        try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
            // submit returns Future โ€” captures exceptions
            List<Future<String>> futures = new java.util.ArrayList<>();
            for (int i = 0; i < 8; i++) {
                final int id = i;
                futures.add(pool.submit(() -> slowTask(id)));
            }

            for (Future<String> f : futures) {
                try {
                    System.out.println(f.get()); // blocks until result ready
                } catch (ExecutionException e) {
                    System.out.println("Task failed: " + e.getCause().getMessage());
                }
            }
        } // pool.close() calls shutdown() + awaitTermination()

        System.out.println("Pool finished");

        // execute: fire-and-forget (exceptions go to UncaughtExceptionHandler)
        try (ExecutorService exec = Executors.newSingleThreadExecutor()) {
            exec.execute(() -> System.out.println("Single-threaded task"));
        }

        // Scheduled: run after delay and periodically
        try (ScheduledExecutorService sched = Executors.newScheduledThreadPool(1)) {
            sched.schedule(() -> System.out.println("Delayed task"), 100, TimeUnit.MILLISECONDS);
            Thread.sleep(200);
        }
    }
}

Sources