Skip to main content
  1. Java Concurrency (java.util.concurrent)/

AbstractExecutorService

4 mins

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 #

View Source on GitHub

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:

  1. Null check: Objects.requireNonNull(task, "task")
  2. Create FutureTask: Call newTaskFor() to wrap the task
  3. Execute: Call the abstract execute() method
  4. 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 newTaskFor to return a Future that 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 ExecutorService API.
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 ...
}