ExecutorService Interface in Java

In this article, we will learn important methods of ExecutorService interface with examples.

ExecutorService Interface Overview

The ExecutorService interface supplements execute with a similar, but more versatile submit method. Like execute, submit accepts Runnable objects, but also accepts Callable objects, which allow the task to return a value. The submit method returns a Future object, which is used to retrieve the Callable return value and to manage the status of both Callable and Runnable tasks.
ExecutorService also provides methods for submitting large collections of Callable objects. Finally, ExecutorService provides a number of methods for managing the shutdown of the executor. To support an immediate shutdown, tasks should handle interrupts correctly.

ExecutorService Interface Source Code from JDK Library

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;
}
Let's list all the methods with a description here.

ExecutorService Interface Methods


  • boolean awaitTermination(long timeout, TimeUnit unit) - Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
  • List<Future> invokeAll(Collection<? extends Callable> tasks) - Executes the given tasks, returning a list of Futures holding their status and results when all complete.
  • List<Future> invokeAll(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) - Executes the given tasks, returning a list of Futures holding their status and results when all complete or the timeout expires, whichever happens first.
  • T invokeAny(Collection<? extends Callable> tasks) - Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do.
  • T invokeAny(Collection<? extends Callable> tasks, long timeout, TimeUnit unit) - Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do before the given timeout elapses.
  • boolean isShutdown() - Returns true if this executor has been shut down.
  • boolean isTerminated() - Returns true if all tasks have completed following shut down.
  • void shutdown() - Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.
  • List shutdownNow() - Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.
  • Future submit(Callable task) - Submits a value-returning task for execution and returns a Future representing the pending results of the task.
  • Future<?> submit(Runnable task) - Submits a Runnable task for execution and returns a Future representing that task.
  • Future submit(Runnable task, T result) - Submits a Runnable task for execution and returns a Future representing that task.

ExecutorService Interface Examples

Let's demonstrates the usage of important ExecutorService interface methods with examples. In this example, how to create an executor service and execute a task inside the executor. We use the Executors.newSingleThreadExecutor() method to create an ExecutorService that uses a single worker thread for executing tasks.
/**
 * ExecutorService interface Example
 * @author javaguides.net
 *
 */
public class ExecutorServiceExample {
    public static void main(String[] args) {

        System.out.println("Thread main started");
  
       // Create a task
        Runnable task = () -> {
             for (int i = 0; i < 5; i++) {
                 System.out.println("[" + Thread.currentThread().getName() + "] " + "Message " + i);
             }
        };

        ExecutorService executorService = Executors.newSingleThreadExecutor();

        executorService.execute(task);

        executorService.shutdown();

        System.out.println("Thread main finished");

     }
}
Output:
Thread main started
Thread main finished
[pool-1-thread-1] Message 0
[pool-1-thread-1] Message 1
[pool-1-thread-1] Message 2
[pool-1-thread-1] Message 3
[pool-1-thread-1] Message 4
let's understand the difference between execute() and submit() methods:

Different Between execute() and submit() Methods

  1. The main difference is submit() method returns Future object for tracking the results but execute() method does't return anthing.
  2. Both submit() and execute() methods are used to submit a task to Executor framework for asynchronous execution.
  3. The submit() can accept both Runnable and Callable task but execute() can only accept the Runnable task.
  4. You can access submit() and execute() from the ExecutorService interface because it also extends the Executor interface which declares the execute() method.
If you run the above program, you will notice that the program never exits, because, the executor service keeps listening for new tasks until we shut it down explicitly.

Shutting down the ExecutorService

ExecutorService provides two methods for shutting down an executor -
  • shutdown() - when shutdown() method is called on an executor service, it stops accepting new tasks, waits for previously submitted tasks to execute, and then terminates the executor.
  • shutdownNow() - this method interrupts the running task and shuts down the executor immediately.
We should add shutdown code at the end of our program so that it exits gracefully -
executorService.shutdown();

ExecutorService example with multiple threads and tasks

Let's use Executors.newFixedThreadPool(int nThreads) method creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
In the earlier example, we created an ExecutorService that uses a single worker thread. But the real power of ExecutorService comes when we create a pool of threads and execute multiple tasks concurrently in the thread pool.
Following example shows how you can create an executor service that uses a thread pool and execute multiple tasks concurrently -
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * ExecutorService interface Example
 * 
 * @author javaguides.net
 *
 */
public class ExecutorServiceExample {
    public static void main(String[] args) {

        System.out.println("Thread main started");

        // Create a task
        Runnable task1 = () -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("[" + Thread.currentThread().getName() + "] " + "Message " + i);
            }
        };

       // Create a task
        Runnable task2 = () -> {
            for (int i = 0; i < 5; i++) {
                 System.out.println("[" + Thread.currentThread().getName() + "] " + "Message " + i);
            }
        };

      // Create a task
       Runnable task3 = () -> {
           for (int i = 0; i < 5; i++) {
               System.out.println("[" + Thread.currentThread().getName() + "] " + "Message " + i);
           }
       };

    // Create a task
      Runnable task4 = () -> {
          for (int i = 0; i < 5; i++) {
              System.out.println("[" + Thread.currentThread().getName() + "] " + "Message " + i);
          }
      };
      ExecutorService executorService = Executors.newFixedThreadPool(2);

      executorService.execute(task1);
      executorService.execute(task2);
      executorService.execute(task3);
      executorService.execute(task4);
 
      System.out.println("Thread main finished");

      executorService.shutdown();
   }
}
Output:
Thread main started
Thread main finished
[pool-1-thread-1] Message 0
[pool-1-thread-1] Message 1
[pool-1-thread-1] Message 2
[pool-1-thread-1] Message 3
[pool-1-thread-1] Message 4
[pool-1-thread-1] Message 0
[pool-1-thread-1] Message 1
[pool-1-thread-1] Message 2
[pool-1-thread-1] Message 3
[pool-1-thread-1] Message 4
[pool-1-thread-1] Message 0
[pool-1-thread-1] Message 1
[pool-1-thread-1] Message 2
[pool-1-thread-1] Message 3
[pool-1-thread-1] Message 4
[pool-1-thread-2] Message 0
[pool-1-thread-2] Message 1
[pool-1-thread-2] Message 2
[pool-1-thread-2] Message 3
[pool-1-thread-2] Message 4
In this above example, we have created 2 thread and executing 4 tasks. Look at the output only 2 threads pool-1-thread-1 and pool-1-thread-2 are reused for executing 4 tasks.

Comments