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.
Enter the URL of a public GitHub repository