
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
– The?ExecutorService
?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