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.
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
- https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/concurrent/ExecutorService.html โ ExecutorService API
- https://docs.oracle.com/javase/tutorial/essential/concurrency/executors.html โ Executors tutorial