Executor framework and its usage

Reading Time: 4 minutes

The Executor Framework contains a bunch of components that are use to efficiently manage worker threads.

The Executor API de-couples the execution of the task from the actual task to execute via Executors.

This design is one of the implementations of the Producer-Consumer pattern

Executors provide factory methods that is use to create ThreadPools of worker threads.

To use the Executor Framework we need to create one such thread pool and submit the task to it for execution. It is the job of the Executor Framework to schedule and execute the submitted tasks and return the results from the thread pool.

Types of Executor

Single Thread Executor

This thread pool executor has only a single thread. It is use to execute tasks in a sequential manner. If the thread dies due to an exception while executing a task, a new thread is create to replace the old thread and the subsequent tasks are executed in the new one.

ExecutorService executorService = Executors.newSingleThreadExecutor()

Fixed Thread Pool

As the name indicates, it is a thread pool of a fixed number of threads. The tasks submitted to the executor are execute by the n threads. There is more task they are store on a LinkedBlockingQueue. This number is usually the total number of threads support by the underlying processor.

ExecutorService executorService = Executors.newFixedThreadPool(4);

Cached Thread Pool

This thread pool is mostly use where there are lots of short-lived parallel tasks to execute. Unlike the fixed thread pool, the number of threads of this executor pool is not bounded. If all the threads are busy executing some tasks and a new task comes, the pool will create and add a new thread to the executor. As soon as one of the threads becomes free, it will take up the execution of the new tasks. If a thread remains idle for sixty seconds, they are terminate and removed from the cache.

However, if not managed correctly, or the tasks are not short-lived, the thread pool will have lots of live threads. This may lead to resource thrashing and hence performance drop.

ExecutorService executorService = Executors.newCachedThreadPool();

Scheduled Executor

This executor is use when we have a task that needs to run at regular intervals or if we wish to delay a certain task.

ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);

The tasks can schedule in ScheduledExecutor using either of the two methods scheduleAtFixedRate or scheduleWithFixedDelay.

scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)

The main difference between the two methods is their interpretation of the delay between consecutive executions of a scheduled job.

scheduleAtFixedRate executes the task with a fixed interval, irrespective of when the previous task ended.

scheduleWithFixedDelay will start the delay countdown only after the current task completes.

Understanding the Future Object

The result of the task submitted for execution to an executor can access using the java.util.concurrent.The future object is return by the executor.

Check out our hands-on, practical guide to learning Git, with best practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

Future result = executorService.submit(callableTask);

A task submitted to the executor is asynchronous i.e. the program execution does not wait for the completion of task execution to proceed to the next step.

The caller can continue executing the main program and when the result of the submitted task needs he can call get() method on this Future object.

If the task is complete the result is an immediate return to the caller or else the caller is block until the execution of this is complete by the executor and the result is compute.

If the caller cannot afford to wait indefinitely before retrieving the result, this wait can time as well.

This is achieve by the Future.get(long timeout, TimeUnit unit) method which throws a TimeoutException if the result is not return in the stipulated timeframe. The caller can handle this exception and continue with the further execution of the program.

If there is an exception when executing the task, the call to get method will throw an ExecutionException.

An important thing with respect to the result return by Future.get() method is that it is return only if the submitted task implements java.util.concurrent.Callable. If the task implements the Runnable interface, the call to .get() will return null once the task is complete.

Another important method is the Future.cancel(boolean mayInterruptIfRunning) method. This method is use to cancel the execution of a submitted task. If the task is already execute, the executor will attempt to interrupt the task execution if the mayInterruptIfRunning flag is pass as true.

import java.util.concurrent.*;
class Task implements Callable<String> {
	// Member variable of this class
	private String message;

	// Constructor of this class
	public Task(String message)
	{
		// This keyword refers to current instance itself
		this.message = message;
	}

	// Method of this Class
	public String call() throws Exception
	{
		return "Hiiii " + message + "!";
	}
}

public class Main {

	// Main driver method
	public static void main(String[] args)
	{

		// Creating an object of above class
		// in the main() method
		Task task = new Task("GeeksForGeeks");

		// Creating object of ExecutorService class and
		// Future object Class
		ExecutorService executorService
			= Executors.newFixedThreadPool(4);
		Future<String> result
			= executorService.submit(task);

		// Try block to check for exceptions
		try {
			System.out.println(result.get());
		}

		// Catch block to handle the exception
		catch (InterruptedException
			| ExecutionException e) {

			// Display message only
			System.out.println(
				"Error occured while executing the submitted task");

			// Print the line number where exception occured
			e.printStackTrace();
		}

		// Cleaning resource and shutting down JVM by
		// saving JVM state using shutdown() method
		executorService.shutdown();
	}
}

Knoldus-blog-footer-image

Written by 

I am a java developer having 6 years of experience. I have worked on Core Java, Spring, Spring boot, Kafka, Spark, MySQL. I am curious about learning new technologies.