January 8, 2023 What is ExecutorService in Java Learn to use Java ExecutorService to execute a Runnable or Callable class in an asynchronous way. Also, learn the various best practices to utilize it in the most efficient manner in any Java application. 1. What is Executor Framework? In simple Java applications, we do not face many challenges while working with a few threads. If we have to develop a program that runs a lot of concurrent tasks, this approach will present many disadvantages such as lots of boilerplate code (create and manage threads), executing threads manually and keeping track of thread execution results. Executor framework (since Java 1.5) solved this problem. The framework consists of three main interfaces (and lots of child interfaces): Executor, ExecutorService ThreadPoolExecutor 1.1. Benefits of Executor Framework The framework mainly separates task creation and execution. Task creation is mainly boilerplate code and is easily replaceable. With an executor, we have to create tasks that implement either Runnable or Callable interface and send them to the executor. Executor internally maintains a (configurable) thread pool to improve application performance by avoiding the continuous spawning of threads. Executor is responsible for executing the tasks, and running them with the necessary threads from the pool. 1.2. Callable and Future Another important advantage of the Executor framework is the use of the Callable interface. It’s similar to the Runnable interface, with two benefits: It’s call() method returns a result after the thread execution is complete. When we send a Callable object to an executor, we get a Future object’s reference. We can use this object to query the status of the thread and the result of the Callable object. 2. Creating Executor Service Instance ExecutorService is an interface and its implementations can execute a Runnable or Callable class in an asynchronous way. Note that invoking the run() method of a Runnable interface in a synchronous way is simply calling a method. We can create an instance of ExecutorService in the following ways: 2.1. Using Executors Executors is a utility class that provides factory methods for creating the implementations of the interface. //Executes only one thread ExecutorService es = Executors.newSingleThreadExecutor(); //Internally manages thread pool of 2 threads ExecutorService es = Executors.newFixedThreadPool(2); //Internally manages thread pool of 10 threads to run scheduled tasks ExecutorService es = Executors.newScheduledThreadPool(10); 2.2. Using Constructors We can choose an implementation class of ExecutorService interface and create its instance directly. The below statement creates a thread pool executor with a minimum thread count 10, maximum threads count 100 and 5 milliseconds keep alive time and a blocking queue to watch for tasks in the future. ExecutorService executorService = new ThreadPoolExecutor(10, 100, 5L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); 3. Submitting Tasks to ExecutorService Generally, tasks are created by implementing either Runnable or Callable interface. Let’s see the example of both cases. 3.1. Executing Runnable Tasks We can execute runnable using the following methods : void execute(Runnable task) – executes the given command at some time in the future. Future submit (Runnable task) – submits a runnable task for execution and returns a Future representing that task. The Future’s get() method will return null upon successful completion. Future submit (Runnable task, T result) – Submits a runnable task for execution and returns a Future representing that task. The Future’s get() method will return the given result upon successful completion. In the given example, we are executing a task of type Runnable using both methods. import java.time.LocalDateTime; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class Main{ public static void main(String[] args){ //Demo task Runnable runnableTask = () -> { try { TimeUnit.MILLISECONDS.sleep(1000); System.out.println("Current time :: " + LocalDateTime.now()); } catch (InterruptedException e) { e.printStackTrace(); } }; //Executor service instance ExecutorService executor = Executors.newFixedThreadPool(10);//1. execute task using execute() method executor.execute(runnableTask); //2. execute task using submit() method Future<String> result = executor.submit(runnableTask, "DONE"); while(result.isDone() == false){ try{ System.out.println("The method return value : " + result.get()); break; } catch (InterruptedException | ExecutionException e){ e.printStackTrace(); } //Sleep for 1 second try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } //Shut down the executor service executor.shutdownNow(); } } output. Current time :: 2019-05-21T17:52:53.274 Current time :: 2019-05-21T17:52:53.274 The method return value : DONE 3.2. Execute Callable Tasks We can execute callable tasks using the following methods : Future submit(callableTask) – submits a value-returning task for execution and returns a Future representing the pending results of the task. List<Future> invokeAll(Collection tasks) – executes the given tasks, returning a list of Futures holding their status and results when all complete. Notice that result is available only when all tasks are completed. Note that a completed task could have terminated either normally or by throwing an exception. List<Future> invokeAll(Collection tasks, timeOut, timeUnit) – executes the given tasks, returning a list of Futures holding their status and results when all complete or the timeout expires. In the given example, we are executing a task of type Callable using both methods. import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; public class Main{ public static void main(String[] args) throws ExecutionException{//Demo Callable task Callable<String> callableTask = () -> { TimeUnit.MILLISECONDS.sleep(1000); return "Current time :: " + LocalDateTime.now(); };//Executor service instance ExecutorService executor = Executors.newFixedThreadPool(1); List<Callable<String>> tasksList = Arrays.asList(callableTask, callableTask, callableTask);//1. execute tasks list using invokeAll() method try{ List<Future<String>> results = executor.invokeAll(tasksList); for(Future<String> result : results){ System.out.println(result.get()); } } catch (InterruptedException e1){ e1.printStackTrace(); }//2. execute individual tasks using submit() method Future<String> result = executor.submit(callableTask); while(result.isDone() == false){ try{ System.out.println("The method return value : " + result.get()); break; } catch (InterruptedException | ExecutionException e){ e.printStackTrace(); }//Sleep for 1 second try { Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } } //Shut down the executor service executor.shutdownNow(); } } output: Current time :: 2019-05-21T18:35:53.512 Current time :: 2019-05-21T18:35:54.513 Current time :: 2019-05-21T18:35:55.514 The method return value : Current time :: 2019-05-21T18:35:56.515 Notice that tasks have been completed with a delay of 1 second because there is only one task in the thread pool. But when you run the program, all the first 3 print statements appear at the same time because even if the tasks are complete, they wait for other tasks to complete in the list. 4. How to shutdown ExecutorService The final and most important thing that many developers miss is shutting down the ExecutorService. The ExecutorService is created and it has Thread elements. Remember that the JVM stops only when all non-daemon threads are stopped. Here, not shutting down the executor service simply prevents the JVM from stopping. In the above examples, if we comment out the executor.shutdownNow() method call, then even after all tasks are executed, the main thread remains active and JVM does not stop. To tell the executor service that there is no need for the threads it has, we will have to shut down the service. There are three methods to invoke shutdown: void shutdown() – Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. List<Runnable> 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. void awaitTermination() – It blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first. Use any of the above 3 methods wisely as per the requirements of the application. Concurrency Java advanced multithreading in javaapplicationsasynchronousasynchronous javaclient-side vs server-sidecomparison of c sharp and javadesigning microservicesdistributed primitivesexecutor framework in javaExecutorServiceexecutorservice in javaexecutorservice interfaceexecutorservice invokeallexecutorservice tutorialsimproves your listening skillsin memory computingin memory data gridjava asynchronous programmingjava executorservicejava executorservice tutoriallearn while on the movelearning by listeningmultithreading in javamultithreading in java in hindimultithreading in java interview questionsmultithreading interview questions in javarest in an async worldrest in an async world - jax-rs client apirestful servicesrestricted titlesscheduleexecutorservice in javasemaphore in javasynchronous vs. asynchronoustext to speechwhat is multithreading in java