How to use Java Executor framework for Multithreading

 · 12 mins read

Photo by Patrick Perkins On Unsplash

In my Previous Blog I covered the basics of Multithreading in Java. Click here to read that blog.

The previous Blog covered how to create Threads by Extending the Thread class and implementing the Runnable Interface.

This article will be covering 2 topics.

  • Creating Threads by implementing the Callable Interface
  • Using the Executor Framework in Java

Implementing the Callable Interface

In order to create a Piece of code which can be run in a Thread, we create a class and then implement the Callable Interface. The task being done by this piece of code needs to be put in the call() function. In the below code you can see that CallableTask is a class which implements Callable Interface, and the task of summing up numbers from 0 to 4 is being done in the function.

import java.util.concurrent.Callable;
class CallableTask implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {

		int sum = 0;
		for (int i = 0; i < 5; i++) {
			sum += i;
		}
		return sum;
	}

}

In the above code you would notice that Callable has a parameter of Integer. This shows that the return type of this Callable will be Integer. Also it can be seen that the call function returns Integer type.

Creating the Threads and running them

The code below shows how to create the Threads and then run them.

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableInterfaceDemo {

	public static void main(String[] args) {
		FutureTask<Integer>[] futureList = new FutureTask[5];

		for (int i = 0; i <= 4; i++) {
			Callable<Integer> callable = new CallableTask();
			futureList[i] = new FutureTask<Integer>(callable);
			Thread t = new Thread(futureList[i]);
			t.start();

		}

		for (int i = 0; i <= 4; i++) {
			FutureTask<Integer> result = futureList[i];
			try {
				System.out.println("Future Task" + i + ":" + result.get());
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			} catch (ExecutionException e) {
				
				e.printStackTrace();
			}
		}

	}

}

In order to create a Thread, first we need to create an Instance of CallableTask which implements the Callable Interface as shown in

Callable<Integer> callable = new CallableTask();

Then we need to create an Instance of the FutureTask class and pass the instance of Callable task as an argument as shown in

futureList[i] = new FutureTask<Integer>(callable);

Then to create a Thread we create an instance of the Thread class and pass the Instance of the FutureTask class as an argument as shown in

Thread t = new Thread(futureList[i]);

Finally the Thread is started with the start() function.

Getting the result from the Thread

In case of Callables the Thread can actually return a value. In order to get this value we can call the get() function on the instance of the FutureTask. In our code, the return value of the thread is the sum of numbers from 0 to 4.

This is shown in the below code snippet

FutureTask<Integer> result = futureList[i];
try {
	System.out.println("Future Task" + i + ":" + result.get());
} catch (InterruptedException e) {
				
	e.printStackTrace();
} catch (ExecutionException e) {
				
	e.printStackTrace();
}

Also the thread may throw an Exception as well which can be handled with try catch blocks.

Complete Code

Here is the complete code discussed till now

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class CallableTask implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {

		int sum = 0;
		for (int i = 0; i < 5; i++) {
			sum += i;
		}
		return sum;
	}

}

public class CallableInterfaceDemo {

	public static void main(String[] args) {
		FutureTask<Integer>[] futureList = new FutureTask[5];

		for (int i = 0; i <= 4; i++) {
			Callable<Integer> callable = new CallableTask();
			futureList[i] = new FutureTask<Integer>(callable);
			Thread t = new Thread(futureList[i]);
			t.start();

		}

		for (int i = 0; i <= 4; i++) {
			FutureTask<Integer> result = futureList[i];
			try {
				System.out.println("Future Task" + i + ":" + result.get());
			} catch (InterruptedException e) {
				
				e.printStackTrace();
			} catch (ExecutionException e) {
				
				e.printStackTrace();
			}
		}

	}

}

The Executor Framework

Creating a Thread on the Fly everytime is Resource Intensive. One good alternative for this is to have some Threads already setup and then allocate our tasks to these threads. This is where the Executors Class and ExecutorService are very useful.

ThreadPool

A Thread pool with 4 threads

The above image shows a Thread pool with 4 threads. Whenever we want any task to be run, we can assign it to these threads. Once the task is complete, the Thread will be freed to take up other tasks.

How to use the Executor Framework

Here is a code which uses the Executor framework.

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;

class Worker implements Callable<Integer> {

	@Override
	public Integer call() throws Exception {

		int sum = 0;
		for (int i = 0; i < 5; i++) {
			sum += i;
		}
		return sum;
	}

}

public class ExecutorDemo {

	public static void main(String[] args) {
		ExecutorService executors = Executors.newFixedThreadPool(4);
		Future<Integer>[] futures = new Future[5];
		Callable<Integer> w = new Worker();
		try {
			for (int i = 0; i < 5; i++) {
				Future<Integer> future = executors.submit(w);
				futures[i] = future;

			}

			for (int i = 0; i < futures.length; i++) {
				try {
					System.out.println("Result from Future " + i + ":" + futures[i].get());
				} catch (InterruptedException e) {

					e.printStackTrace();
				} catch (ExecutionException e) {

					e.printStackTrace();
				}
			}
		} finally {
			executors.shutdown();
		}

	}

}

First we create a Worker Class which implements Callable and does the task which we need.

Next we need to create an ExecutorService.

The Executors class has multiple implementations of the ExecutorService.

Let us use the Executors class to create a fixed Thread pool of size 4. This is done as follows

ExecutorService executors = Executors.newFixedThreadPool(4);

Next we need to submit our task to the Executor Service. This is done using the following Line of code

Future<Integer> future = executors.submit(w);

On submitting the task we get an Instance of the Future Object. The Future Object is what will store the result of the Task.

Getting the result of the Thread

In order to get the result of each task, we can call the get() method of the Future Instance. This is shown in the below code snippet.

try {
	System.out.println("Result from Future " + i + ":" + futures[i].get());
} catch (InterruptedException e) {

	e.printStackTrace();
} catch (ExecutionException e) {

	e.printStackTrace();
}

The thread can also Throw an Exception which can be handled using try catch.

Some possible Scenarios of the Fixed Thread Pool

  • In this example we created a fixed Thread pool of size 4
  • If we submit a total of 3 tasks to this ExecutorService, then all 3 tasks will be assigned to the thread pool and they will start executing.
  • If we submit 4 tasks to this ExecutorService, then again all 4 tasks will be assigned to the thread pool and they will start executing.
  • Now if we submit 5 tasks to this thread pool. Only 4 of the tasks will be assigned to the thread pool. This is because the size of the Thread pool is 4. The 5th Task will be assigned only when one of the threads in the pool gets freed

Shutting down the ExecutorService

The ExecutorService needs to be shutdown when the threads are not needed anymore. This will ensure that the JVM is not consuming Additional Resources.

The ExecutorService can be shutdown using the following command

executors.shutdown();

It can be seen that this shutdown is put within the finally block. This is to ensure that the shutdown is always executed at the end of the code even if any exception occured.

If the shutdown is not done in the right way, then in case any exception occurs then the ExecutorService will still be running and will be consuming Additional JVM resources.

Code

All the code discussed in this article can be found in this git repo

Congrats😃

Now you know the following concepts

  • Using the Callable Interface
  • Using the Java Executor Framework for Multithreading.

In my future Articles I will be covering more topics on Multithreading

Feel free to connect with me in LinkedIn or follow me in Twitter