Executor Services and the Concurrent API
Overview
The java.util.concurrent package provides high-level abstractions for managing threads. Instead of creating and managing threads manually, submit tasks to an ExecutorService and let it handle lifecycle.
ExecutorService
javaExecutorService exec = Executors.newFixedThreadPool(4);
// submit a Runnable — returns Future<?>
Future<?> f1 = exec.submit(() -> System.out.println("task"));
// submit a Callable — returns Future<V>
Future<Integer> f2 = exec.submit(() -> 6 * 7);
// execute a Runnable — fire-and-forget, no Future
exec.execute(() -> System.out.println("no future"));
exec.shutdown();Executors Factory Methods
| Factory Method | Description |
|---|---|
newFixedThreadPool(n) |
Pool of exactly n platform threads; tasks queue if all busy |
newCachedThreadPool() |
Grows unboundedly; idle threads recycled after 60 s |
newSingleThreadExecutor() |
One thread, tasks execute in submission order |
newVirtualThreadPerTaskExecutor() |
New virtual thread per task; no pooling |
newScheduledThreadPool(n) |
Fixed pool for scheduled/repeated tasks |
javaExecutorService fixed = Executors.newFixedThreadPool(4);
ExecutorService cached = Executors.newCachedThreadPool();
ExecutorService single = Executors.newSingleThreadExecutor();
ExecutorService virtual = Executors.newVirtualThreadPerTaskExecutor();submit() vs execute()
submit() |
execute() |
|
|---|---|---|
| Accepts | Runnable or Callable |
Runnable only |
| Returns | Future<?> / Future<V> |
void |
| Exception handling | Exceptions stored in Future; thrown on get() |
Uncaught exceptions go to UncaughtExceptionHandler |
Future<V>
javaFuture<String> future = exec.submit(() -> fetchData());
future.isDone(); // true if completed, cancelled, or threw an exception
future.isCancelled(); // true if cancelled before completion
// blocks until result is available
String result = future.get();
// blocks with timeout — throws TimeoutException if not done in time
String result2 = future.get(2, TimeUnit.SECONDS);
// cancel — true to interrupt if running
future.cancel(true);Exam tip:
Future.get()throwsExecutionException(wrapping the task's exception) if the task threw, andCancellationExceptionif it was cancelled.
shutdown() vs shutdownNow()
| Method | Behaviour |
|---|---|
shutdown() |
Stops accepting new tasks; waits for submitted tasks to finish |
shutdownNow() |
Attempts to interrupt running tasks; returns list of queued (never-started) tasks |
awaitTermination(timeout, unit) |
Blocks until all tasks finish or timeout elapses |
isShutdown() |
True after shutdown() or shutdownNow() called |
isTerminated() |
True after all tasks complete post-shutdown |
javaexec.shutdown();
if (!exec.awaitTermination(10, TimeUnit.SECONDS)) {
exec.shutdownNow();
}invokeAll() and invokeAny()
javaList<Callable<Integer>> tasks = List.of(
() -> 1,
() -> 2,
() -> 3
);
// invokeAll — submits all, blocks until ALL complete; returns List<Future<V>>
List<Future<Integer>> results = exec.invokeAll(tasks);
for (Future<Integer> f : results) {
System.out.println(f.get()); // 1, 2, 3
}
// invokeAny — returns result of the FIRST successful task; cancels the rest
Integer first = exec.invokeAny(tasks); // 1, 2, or 3Exam tip:
invokeAll()always returns aListof completedFutures (done or failed).invokeAny()throwsExecutionExceptionif all tasks fail.
ScheduledExecutorService
javaScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
// run once after a delay
scheduler.schedule(() -> System.out.println("delayed"), 3, TimeUnit.SECONDS);
// run repeatedly — fixed rate (next run starts based on initial start time)
scheduler.scheduleAtFixedRate(() -> System.out.println("rate"), 0, 1, TimeUnit.SECONDS);
// run repeatedly — fixed delay (next run starts after previous finishes)
scheduler.scheduleWithFixedDelay(() -> System.out.println("delay"), 0, 1, TimeUnit.SECONDS);
scheduler.shutdown();| Method | Initial Delay | Period Measurement |
|---|---|---|
scheduleAtFixedRate |
from start of execution | from start of previous run |
scheduleWithFixedDelay |
from end of execution | from end of previous run |
try-with-resources and AutoCloseable
ExecutorService implements AutoCloseable since Java 19. close() calls shutdown() and awaits termination.
javatry (ExecutorService exec = Executors.newFixedThreadPool(4)) {
exec.submit(task1);
exec.submit(task2);
} // automatically shuts down and awaits terminationKey Rules
- Always shut down an
ExecutorService; orphaned pools prevent JVM exit. submit(Callable)returns a typedFuture<V>;submit(Runnable)returnsFuture<?>.execute()does not return aFuture— there is no way to check completion or retrieve exceptions.invokeAll()blocks until all tasks complete;invokeAny()returns as soon as one succeeds.- Virtual thread executors should not be used for CPU-bound tasks that hold threads continuously.