Skip to content

Thread executor in Java

Thread executor in Java

1. Overview

In this article, we will learn about the Thread executor in Java.

The Concurrency API was first introduced with the Java 5 release and then steadily enhanced with every new release. This Concurrency API introduces the ExecutorService as a higher-level abstraction for working with threads directly.

The ExecutorService is the interface that allows executing the tasks on threads asynchronously and is available in the java.util.concurrent package.

This interface maintains a pool of threads to execute the incoming tasks. Also, the tasks wait in the ExecutorService queue when the threads in the pool are busy executing other tasks.

2. Thread executor in Java

The ExecutorService is an interface and the following implementations are available in the java.util.concurrent package:

  1. DelegatedExecutorService
  2. ThreadPoolExecutor
  3. ScheduledThreadPoolExecutor
Factory methods of Executor class

However, you can create your own implementation. The Executors class provides convenient factory methods to create an instance of the ExecutorService.

For example, the following newSingleThreadExecutor factory method creates an instance of ExecutorService with a single thread.

ThreadPoolExecutor executor = Executors.newSingleThreadExecutor();

3. Callable and Runnable

The ExecutorService can execute Runnable and Callable tasks.

The Runnable interface represents a task that a thread or an ExecutorService can execute concurrently. The Callable is a task that only an ExecutorService can execute.

Runnable interface declaration:

public interface Runnable {
    public void run();
}

Callable interface declaration:

public interface Callable{
    public Object call() throws Exception;
}

The primary difference between the Runnable and the Callable is that the latter can return the result (Object) of the asynchronous task and throw checked exceptions. However, Runnable throws only unchecked exceptions (subclasses of RuntimeException).

If you need the result of the task, then use Callable with the ExecutorService. Otherwise, use the Runnable interface.

4. ExecutorService Usage

The ExecutorService provides various methods to execute a task asynchronously:

  • execute
  • submit
  • invokeAny
  • invokeAll

Let’s see examples for each of these methods.

4.1. ExecutorService execute method

The execute method of the ExecutorService supports only Runnable. Since the Runnable returns nothing, the return type of the execute method is void.

ExecutorService executor = Executors.newSingleThreadExecutor();
        
executor.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("Executing runnable : " +
               Thread.currentThread().getName());
    }
});

4.2. ExecutorService submit method

ExecutorService submit method

The following overloaded submit methods are available:

  1. Future<?> submit(Runnable task)
  2. Future<T> submit(Callable<T> task)
  3. Future<T> submit(Runnable task, T result)

The submit method supports both Runnable and Callable interfaces and returns Future. The Future is basically a placeholder to hold the result of a task that is not completed yet.

Future represents the result of an asynchronous computation that allows us to check the task completion, wait for it to complete, and retrieve the result of the computation. You can refer to our article on Future for a more detailed explanation.

4.2.1. Submit with Runnable

In the below example, the submit method submits a Runnable to the ExecutorService. We are waiting for the execution to complete by using the blocking get method of Future. However, the get method prints null as the Runnable returns nothing.

Future<?> future = executorService.submit(new Runnable() {
            public void run() {
                System.out.println("Executing task : " + Thread.currentThread().getName());
            }
        });

        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

You can return a static result for the Runnable by using the submit method (Future<T> submit(Runnable task, T result) ).

Future<?> future = executorService.submit(new Runnable() {
            public void run() {
                System.out.println("Executing task : " + Thread.currentThread().getName());
            }
        }, "SUCCESS");

        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

4.2.2. Submit with Callable

The Java submit(Callable) method takes Callable interface as an argument and returns Future.

The result of the Callable task can be retrieved by using the Future.

Future<Integer> future = executorService.submit(new Callable<Integer>() {
            public Integer call() {
                System.out.println("Executing task : " + Thread.currentThread().getName());
                int value = 35;
                return value;
            }
        });

try {
    System.out.println(future.get());
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

4.2.3. invokeAny()

The invokeAny, a blocking call takes a collection of tasks which are of type Callable. This method does not return a Future, but returns the result of one of the Callable tasks. However, you have no guarantee about which of the Callable‘s results you get. The invokeAny cancels other tasks that are not completed.

Here, we are using ThreadPool of 5 threads as we are going to execute more than one task.

ExecutorService executorService = Executors.newFixedThreadPool(5);

For example, the following code contains a list of Callable tasks.

 List<Callable<Object>> callableList = new ArrayList<>();
        callableList.add(new Callable<Object>() {
            public Integer call() {
                System.out.println("Executing task A : " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Completing task B : " + Thread.currentThread().getName());
                int value = 1000;
                return value;
            }
        });
        callableList.add(new Callable<Object>() {
            public Integer call() {
                System.out.println("Executing task B : " + Thread.currentThread().getName());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Completing task B : " + Thread.currentThread().getName());
                int value = 500;
                return value;
            }
        });

Task A takes approximately 1s to complete whereas task B would take only 500ms.

The invokeAny methods take both A and B tasks and wait for one of these tasks to complete. Since B completes before A, the result of B is returned by the invokeAny.

try {
     Object result = executorService.invokeAny(callableList);
     System.out.println(result);
    } catch (InterruptedException | ExecutionException e) {
     e.printStackTrace();
}

If you execute the above code, then the following result is printed on the console.

Executing task A : pool-1-thread-1
Executing task B : pool-1-thread-2
Completing task B : pool-1-thread-2
500
Completing task A : pool-1-thread-1
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.tedblob.concurrency.ExecutorServiceInvokeAnyDemo$1.call(ExecutorServiceInvokeAnyDemo.java:17)
	at com.tedblob.concurrency.ExecutorServiceInvokeAnyDemo$1.call(ExecutorServiceInvokeAnyDemo.java:13)

4.2.4. invokeAll()

The invokeAll method executes all the Callable objects passed as an argument and return a list of Future objects through which you can wait or get the results of each Callable.

try {
    List<Future<Object>> result = executorService.invokeAll(callableList);
} catch (InterruptedException e) {
    e.printStackTrace();
}

5. Cancel Task

You can cancel a task (Runnable or Callable) submitted to the ExecutorService by calling the cancel method on the returned Future.

You can cancel the task only before it starts its execution.

For example, the Callable task started to execute before triggering the cancel.

Future<Integer> future = executorService.submit(new Callable<Integer>() {
     public Integer call() {
         System.out.println("Executing task : " + Thread.currentThread().getName());
         try {
             Thread.sleep(5000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         int value = 5000;
         System.out.println("Finishing task : " + Thread.currentThread().getName());

         return value;
    }
});

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
future.cancel(true);
Executing task : pool-1-thread-1
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.tedblob.concurrency.ExecutorServiceCancelDemo$1.call(ExecutorServiceCancelDemo.java:16)
	at com.tedblob.concurrency.ExecutorServiceCancelDemo$1.call(ExecutorServiceCancelDemo.java:12)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
	at java.base/java.lang.Thread.run(Thread.java:832)
Finishing task : pool-1-thread-1

6. Shutdown the ExecutorService

After using the ExecutorService, shut it down to prevent the threads from running. The active threads inside the ExecutorService prevent the JVM from shutting down.

There are various methods available to shut down the ExecutorService.

  1. shutdown – The ExecutorService shuts down once all threads finishes executing the current tasks. However, it will no longer accept any new tasks.
  2. shutdownNow – Shuts down immediately and attempts to stop all the current executing tasks.
  3. awaitTermination – Blocks the current thread until the ExecutorService shuts down completely or provided timeout exceeds.

7. Conclusion

To sum up, we have learned the ExecutorService with examples.

Leave a Reply

Your email address will not be published. Required fields are marked *