Properly manage executor service lifecycle to prevent resource leaks, deadlocks, and orphaned threads that can cause system inconsistencies.
Key practices:
invokeAny() over low-level submit().get() with manual cancellationPhaser instead of combining multiple lower-level mechanismsExample of proper executor management:
try (var executor = Executors.newSingleThreadExecutor(threadFactory)) {
// Submit tasks and get results within the try block
var future = executor.submit(callable);
return future.get(timeout, TimeUnit.SECONDS);
} // Executor automatically shuts down here
For cancellation scenarios, prefer structured approaches:
// Instead of manual future cancellation
var future = executor.submit(task);
future.cancel(true);
// Use invokeAny for timeout/cancellation semantics
return executor.invokeAny(List.of(task), timeout, TimeUnit.SECONDS);
This prevents common issues like deadlocks when futures are never scheduled, ensures proper resource cleanup, and avoids race conditions from orphaned threads modifying shared state after new operations have begun.