AbstractExecutorService
AbstractExecutorService is a skeletal implementation of ExecutorService. It provides the “heavy lifting” for methods like submit, invokeAny, and invokeAll, allowing concrete subclasses (like ThreadPoolExecutor) to focus solely on the execute method.
Source Code & Implementation #
The Template Method Pattern #
AbstractExecutorService uses the Template Method pattern through the newTaskFor method. This method creates the RunnableFuture (usually a FutureTask) that wraps the submitted task.
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
Subclasses can override newTaskFor to provide custom Future implementations.
Implementation Details #
submit() Methods #
The three submit() methods all follow the same pattern:
- Null check:
Objects.requireNonNull(task, "task") - Create FutureTask: Call
newTaskFor()to wrap the task - Execute: Call the abstract
execute()method - Return: Return the FutureTask
@Override
public Future<?> submit(Runnable task) {
Objects.requireNonNull(task, "task");
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
invokeAny() Implementation #
The invokeAny() method uses an ExecutorCompletionService to efficiently manage task completion:
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
// ...
ExecutorCompletionService<T> ecs = new ExecutorCompletionService<T>(this);
// Start one task immediately
futures.add(ecs.submit(it.next()));
// Interleave: check for completion while submitting more tasks
for (;;) {
Future<T> f = ecs.poll();
if (f == null) {
if (ntasks > 0) {
futures.add(ecs.submit(it.next()));
} else if (active == 0)
break;
else if (timed) {
f = ecs.poll(nanos, NANOSECONDS);
if (f == null)
throw new TimeoutException();
} else
f = ecs.take();
}
if (f != null) {
try {
return f.get();
} catch (ExecutionException eex) {
ee = eex; // Remember exception to throw later
}
}
}
// If we get here, all tasks failed
if (ee == null)
ee = new ExecutionException();
throw ee;
}
Key optimizations:
- Interleaving: Checks for task completion while submitting new tasks
- Early termination: Returns immediately when any task completes
- Exception handling: Collects exceptions to throw if all tasks fail
- Resource cleanup: Uses
finally { cancelAll(futures); }to cancel remaining tasks
invokeAll() Implementation #
The invokeAll() method submits all tasks and waits for completion:
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException {
ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());
try {
// Submit all tasks
for (Callable<T> t : tasks) {
RunnableFuture<T> f = newTaskFor(t);
futures.add(f);
execute(f);
}
// Wait for all to complete
for (int i = 0, size = futures.size(); i < size; i++) {
Future<T> f = futures.get(i);
if (!f.isDone()) {
try { f.get(); }
catch (CancellationException | ExecutionException ignore) {}
}
}
return futures;
} catch (Throwable t) {
cancelAll(futures);
throw t;
}
}
The timed version adds timeout handling and careful nanos calculation:
final long deadline = System.nanoTime() + nanos;
for (int i = 0; i < size; i++) {
if (((i == 0) ? nanos : deadline - System.nanoTime()) <= 0L)
break timedOut;
execute((Runnable)futures.get(i));
}
Exception Handling & Cleanup #
Both invokeAny() and invokeAll() use the cancelAll() helper method:
private static <T> void cancelAll(ArrayList<Future<T>> futures) {
cancelAll(futures, 0);
}
private static <T> void cancelAll(ArrayList<Future<T>> futures, int j) {
for (int size = futures.size(); j < size; j++)
futures.get(j).cancel(true); // interrupt if running
}
This ensures proper resource cleanup when:
- An exception occurs during submission
- A timeout expires
- The method completes normally
Canonical Usage #
When to use: Use AbstractExecutorService when you want to build a custom executor without having to reimplement the complex logic of submit, invokeAny, and invokeAll.
Common Patterns:
- Custom Future Implementations: Override
newTaskForto return aFuturethat supports additional logging, auditing, or metrics gathering. - Custom Execution Strategies: Create an executor that runs tasks in a special context (e.g., within a specific thread local environment) but still provides the full
ExecutorServiceAPI.
public class LoggingExecutor extends AbstractExecutorService {
private final Executor delegate;
public LoggingExecutor(Executor delegate) {
this.delegate = delegate;
}
@Override
public void execute(Runnable r) {
delegate.execute(() -> {
System.out.println("Starting task: " + r);
try { r.run(); }
finally { System.out.println("Finished task: " + r); }
});
}
// ... other required methods for a full implementation ...
}