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. |
|