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:
DelegatedExecutorService
ThreadPoolExecutor
ScheduledThreadPoolExecutor
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
The following overloaded submit methods are available:
Future<?> submit(Runnable task)
Future<T> submit(Callable<T> task)
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.
A 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
.
shutdown
– TheExecutorService
shuts down once all threads finishes executing the current tasks. However, it will no longer accept any new tasks.shutdownNow
– Shuts down immediately and attempts to stop all the current executing tasks.awaitTermination
– Blocks the current thread until theExecutorService
shuts down completely or provided timeout exceeds.
7. Conclusion
To sum up, we have learned the ExecutorService
with examples.
Pingback: Mono.fromCallable Java with example - TedBlob
Pingback: Mono fromCallable vs fromSupplier - TedBlob
Pingback: RxJava fromRunnable operator - TedBlob