Signup/Sign In

Java ExecutorService

The ExecutorService is an interface used to execute asynchronous tasks concurrently in the background. The ExecutorService maintains a pool of threads and assigns tasks to these threads. It also maintains a queue to store tasks when no free threads are available.

In this tutorial, we will learn how to use the ExecutorService in Java.

Creating an ExecutorService

We can use three different factory methods of the Executors class to create an ExecutorService.

  • The newSingleThreadExecutor() method creates an Executor with a single thread.
  • The newFixedThreadPool(int poolSize) method creates an Executor with the fixed number of threads mentioned by the poolSize.
  • The newScheduledThreadPool(int poolSize) method creates an Executor with the mentioned thread pool size which can schedule tasks after a given delay. It can also execute tasks periodically.

The following code demonstrates the working of these methods.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable task = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task...");
		    }
		};		
		ExecutorService exeService1 = Executors.newSingleThreadExecutor();
		exeService1.execute(task);
		
		ExecutorService exeService2 = Executors.newFixedThreadPool(5);
		exeService2.execute(task);
		
		ExecutorService exeService3 = Executors.newScheduledThreadPool(5);	
		exeService3.execute(task);
	}
}


Performing a Task...
Performing a Task...
Performing a Task...

We can also choose one of the ExecutorService implementations, like the ThreadPoolExecutor class or the ScheduledThreadPoolExecutor class.

Assigning Tasks to the ExecutorService

We can use the execute(), submit(), invokeAny(), or the invokeAll() methods to assign and execute tasks with ExecutorService.

The execute() Method

The execute() method takes a Runnable task as a parameter and executes it asynchronously. It will not return anything, and we cannot check the task status.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable task = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task...");
		    }
		};
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();
				
		exeService.execute(task);
	}
}


Performing a Task...

The submit() Method

The submit() method can execute a Runnable or Callable task. Unlike execute(), it will return the task status in the form of a Future object. We can use the get() method on the Future object to check the task status. The get() method will return null if the Runnable task is executed successfully and the call() method's result for Callable tasks.

For Runnable task:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo
{
	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		Runnable task = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Runnable Task...");
		    }
		};		
		ExecutorService exeService = Executors.newSingleThreadExecutor();
				
		Future status = exeService.submit(task);
		System.out.println("Status: " + status.get());
	}
}


Performing a Runnable Task...
Status: null

For Callable task

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;

public class Demo
{
	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		Callable task = new Callable() {
		    public Object call() throws Exception{
		        System.out.println("Performing a Callable Task...");
		        return "Callable Task Performed";
		    }
		};
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();
				
		Future status = exeService.submit(task);
		System.out.println("Status: " + status.get());
	}
}


Performing a Callable Task...
Status: Callable Task Performed

The invokeAny() Method

The invokeAny() method takes a collection of Callable tasks and executes them. It will return the status of any of the successfully executed tasks. The status is returned by using Future objects.

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo
{
	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		//Creating callable tasks
		Callable task1 = new Callable() {
		    public Object call() throws Exception{
		        System.out.println("Performing a Callable Task1...");
		        return "Callable Task1 Performed";
		    }
		};		
		Callable task2 = new Callable() {
		    public Object call() throws Exception{
		        System.out.println("Performing a Callable Task2...");
		        return "Callable Task2 Performed";
		    }
		};		
		Callable task3 = new Callable() {
		    public Object call() throws Exception{
		        System.out.println("Performing a Callable Task3...");
		        return "Callable Task3 Performed";
		    }
		};
		//Creating a list of callable tasks
		ArrayList<Callable<String>> callableTasks = new ArrayList<>();
		callableTasks.add(task1);
		callableTasks.add(task2);
		callableTasks.add(task3);
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();
		String status = exeService.invokeAny(callableTasks);
		System.out.println("Status: " + status);
	}
}


Performing a Callable Task1...
Performing a Callable Task2...
Performing a Callable Task3...
Status: Callable Task1 Performed

The invokeAll() method also takes a collection of Callable tasks and executes them. But unlike the invokeAny() method, it will return a list containing the status of all the executed tasks. The status is returned by using Future objects.

import java.util.ArrayList;
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;

public class Demo
{
	public static void main(String[] args) throws InterruptedException, ExecutionException
	{
		//Creating callable tasks
		Callable task1 = new Callable() {
		    public Object call() throws Exception{
		        return "Callable Task1 Performed";
		    }
		};
		
		Callable task2 = new Callable() {
		    public Object call() throws Exception{
		        return "Callable Task2 Performed";
		    }
		};
		
		Callable task3 = new Callable() {
		    public Object call() throws Exception{
		        return "Callable Task3 Performed";
		    }
		};
		//Creating a list of callable tasks
		ArrayList<Callable<String>> callableTasks = new ArrayList<>();
		callableTasks.add(task1);
		callableTasks.add(task2);
		callableTasks.add(task3);
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();
		List<Future<String>> status = exeService.invokeAll(callableTasks);
				
		//Printing the status
		for(Future f : status)
			System.out.println(f.get());
	}
}


Callable Task1 Performed
Callable Task2 Performed
Callable Task3 Performed

Shutdown an ExecutorService

An ExecutorService will not automatically shut down when it has no more tasks to perform. It will continue to wait for new tasks that may come up in the future. The active threads inside the ExecutorService will continue to run and will not allow the JVM to stop.

shutdown() Method

We can use the shutdown() method to stop an ExecutorService. This method will prevent ExecutorService from taking new tasks. The ExecutorService will shut down when all the existing tasks are done.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable task1 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task1...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		Runnable task2 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task2...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		Runnable task3 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task3...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();

		exeService.execute(task1);
		exeService.execute(task2);
		exeService.execute(task3);
		
		exeService.shutdown();//All Tasks will be executed
	}
}


Performing a Task1...
Performing a Task2...
Performing a Task3...

shutdownNow() Method

The shutdownNow() method is used to stop the running tasks immediately. This method will do its best to stop all the running tasks, but there is no guarantee.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable task1 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task1...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		Runnable task2 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task2...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		Runnable task3 = new Runnable() {
		    public void run() {
		        System.out.println("Performing a Task3...");
		        try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					System.out.print(e);
				}
		    }
		};
		
		ExecutorService exeService = Executors.newSingleThreadExecutor();

		exeService.execute(task1);
		exeService.execute(task2);
		exeService.execute(task3);
		
		exeService.shutdownNow();//Only a few Tasks will be executed
	}
}


Performing a Task1...
java.lang.InterruptedException: sleep interrupted

awaitTermination() Method

The recommended approach to shut down an ExecutorService is to use the shutdown() and shutdownNow() methods with the awaitTermination() method.

First, the ExecutorService will stop taking new tasks using the shutdown() or shutdownNow() methods. Then, the awaitTermination() will either wait until all tasks finish execution or the timeout occurs, whatever comes first. A common implementation of the awaitTermination() method is shown below.

public static void shutdownUsingAwaitTermination(ExecutorService exeService)
{
	exeService.shutdown();
	try {
		//Wait till timeout for existing tasks to end
		if(!exeService.awaitTermination(10, TimeUnit.SECONDS))//Wait till timeout for existing tasks to end
			exeService.shutdownNow();//Cancel executing tasks
			
		if(!exeService.awaitTermination(20, TimeUnit.SECONDS))//Wait for the tasks to be cancelled
			System.err.println("ExecutorService did not terminate");
		}
	catch(Exception e){
		exeService.shutdownNow();
	}
}

The ScheduledExecutorService Interface

The ScheduledExecutorService can run a task after a certain delay. It can also run a task repeatedly, at a fixed rate, or after a fixed delay.

Let's execute a Runnable task after an initial delay of 5 seconds. We will use the schedule() command for this.

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable runnableTask = new Runnable() {
			public void run()
			{
				System.out.println("Task Performed At: " + new Date());
			}
		};
		
		System.out.println("Current Date and Time: " + new Date());
	    long delay  = 5;
	    ScheduledExecutorService exeService = Executors.newSingleThreadScheduledExecutor();
	    exeService.schedule(runnableTask, delay, TimeUnit.SECONDS);
	}
}


Current Date and Time: Thu Aug 05 11:22:12 IST 2021
Task Performed At: Thu Aug 05 11:22:17 IST 2021

Let's repeat a certain task after every 5 seconds. The scheduleAtFixedRate() method is used for this. Note that the repeatPeriod will work according to absolute time. The commencement of the next task is not affected by the time of termination of the previous task.

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable runnableTask = new Runnable() {
			public void run()
			{
				System.out.println("Task Performed At: " + new Date());
			}
		};		
		System.out.println("Current Date and Time: " + new Date());
	    long delay  = 1;
	    long repeatPeriod = 5;
	    ScheduledExecutorService exeService = Executors.newSingleThreadScheduledExecutor();
	    exeService.scheduleAtFixedRate(runnableTask, delay, repeatPeriod, TimeUnit.SECONDS);
	}
}


Current Date and Time: Thu Aug 05 11:23:29 IST 2021
Task Performed At: Thu Aug 05 11:23:30 IST 2021
Task Performed At: Thu Aug 05 11:23:35 IST 2021
Task Performed At: Thu Aug 05 11:23:40 IST 2021
Task Performed At: Thu Aug 05 11:23:45 IST 2021

If we want to introduce a fixed delay between the termination of one task and the commencement of the next one, then we would use the scheduleWithFixedDelay() method.

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class Demo
{
	public static void main(String[] args) throws InterruptedException
	{
		Runnable runnableTask = new Runnable() {
			public void run()
			{
				System.out.println("Task Performed At: " + new Date());
			}
		};		
		System.out.println("Current Date and Time: " + new Date());
	    long delay  = 1;
	    long repeatPeriod = 5;
	    ScheduledExecutorService exeService = Executors.newSingleThreadScheduledExecutor();
	    exeService.scheduleWithFixedDelay(runnableTask, delay, repeatPeriod, TimeUnit.SECONDS);
	}
}


Current Date and Time: Thu Aug 05 11:24:23 IST 2021
Task Performed At: Thu Aug 05 11:24:24 IST 2021
Task Performed At: Thu Aug 05 11:24:29 IST 2021
Task Performed At: Thu Aug 05 11:24:34 IST 2021

Summary

The ExecutorService in Java is used to execute tasks asynchronously. There are a few different ways of initializing the ExecutorService. We should always use the appropriate pool size when initializing an ExecutorService using the newFixedThreadPool() method. Smaller pool sizes increase the wait time of the tasks, and larger pool sizes lead to performance overheads.

We can also execute the tasks using different methods like execute() or submit(). We should also use the Future interface cautiously. Trying to use the get() method on a canceled task will result in a CancellationException. It is also a good idea to use a timeout with the get() method of the Future interface. Remember to shut down an ExecutorService after use.



About the author:
I am a 3rd-year Computer Science Engineering student at Vellore Institute of Technology. I like to play around with new technologies and love to code.