Close

Java - The Executor Framework

[Last Updated: Oct 3, 2018]

Executor framework is a flexible task dispatching framework. The framework abstracts away thread creation and managing it's lifecycle plus adding some extra thread-management related functionality depending on what type of the "Executor" we use.

Instead of learning a lot of theory, let's take a practical approach and see what we can do with it.

Here are the part of java.util.concurrent package showing executor framework related artifacts.


Interfaces:



Classes:



The Executor interface

The framework is based on java.util.concurrent.Executor interface:

 package java.util.concurrent;
   public interface Executor {
   void execute(Runnable command);
}

Conceptually, calling Executor#execute(command) method has the same effect as: (new Thread(command)).start();



The ExecutorService interface

A sub interface of Executor, which adds features that help manage the lifecycle, both of the individual tasks and of the executor itself.

package java.util.concurrent;

import java.util.List;
import java.util.Collection;

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Submit methods

Similar to Executor#execute, the ExecutorService interface defines more versatile submit method.

Understanding Callable interface

Like execute(), the submit() method accepts Runnable objects, but also accepts Callable objects, which allows the task to return a value and handle exception gracefully.

package java.util.concurrent;

 public interface Callable<V> {
    V call() throws Exception;
 }

Understanding Future interface

The submit method returns a Future object, which is used to retrieve the Callable returned value and to manage the status of both Callable and Runnable tasks.

package java.util.concurrent;

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Bulk Submission

The ExecutorService provides methods (invokeAll, invokeAny) for submitting large collections of Callable objects.

Managing ExecutorService lifecycle

ExecutorService also provides a number of methods for managing service's lifecycle, specifically shutdown of the executor.

The Executors class

Based on factory pattern, this class has static methods for creating instances of ExecutorService. We will rarely want to create the service instance by using new operator. It's very uncommon that we have to deal with various implementation of ExecutorService directly as this class abstracts away all implementation details, we just have to work with high level returned interface instances (the main advantage of using factory pattern!).


Example

Let's see a quick example. In this example we are going to use Executors#newSingleThreadExecutor method, which is the simplest method to get started.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class SingleThreadExecutorExample {

    public static void main (String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<?> future = executorService.submit(new Runnable() {
            @Override
            public void run () {
                System.out.println("task running");
            }
        });

        executorService.shutdown();
    }
}

Output:

    task running


In Executors class, all methods of form newXYZ return an instance of ExecutorService or a sub-interface instance.

In above example we submitted a runnable, which is executed asynchronously.

The submitted call returns a java.util.concurrent.Future instance. We are not doing anything with it in above example but it's useful in cases where we want to check if the thread is done executing, to get the return value in case if we use java.util.concurrent.Callable as parameter and to cancel the task.

The shutdown() call allows the submitted tasks to execute before terminating the service.



Executors commonly used methods

Following are other Executors#newXYZ methods which we will be covering in next tutorials:

Executors method Description
newCachedThreadPool(..) Creates a cached thread pool's ExecutorService that can create new threads as needed or reused cached thread.
newFixedThreadPool(..) Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
newScheduledThreadPool(..) Creates a thread pool that can schedule commands to run after a given delay, or to execute periodically.
newSingleThreadExecutor(..) Creates an Executor that uses a single worker thread operating off an unbounded queue. We have already seen an example above.
newSingleThreadScheduledExecutor(..) Creates a single-threaded executor that can schedule commands to run after a given delay, or to execute periodically.
newWorkStealingPool(..) Creates a work-stealing thread pool using all available processors as its target parallelism level. May use multiple queues to reduce contention.

See Also