How to implement Concurrency in Java – A very minimalist example

Photo by Snapwire on Pexels.com

Let’s say you are implementing a batch service and you want to execute a lot of methods. Let’s assume they are independent functionalities and the output of one is not used by any other methods. Executing them one by one will consume a lot of time. The best strategy is to execute them in parallel. Java provides a number of ways to implement concurrency . Executor Service is the simplest and the easiest to use.

Let’s take a look at a very minimalist example.

To run functionalities in parallel using an executor service you need to follow the below steps :

  • Create an Executor service instance
  • Pass Callable instances to the submit() method of the above instance
  • Shut the executor service down once you are done (it is not done automatically)

That’s it!

I have left out other possibilities in this post for simplicity. So this post takes an opinionated approach.

Let’s took at a sample code:

package executor;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorDemo {

	public static void main(String a[]) {

		ExecutorService service = Executors.newCachedThreadPool();

		Callable<String> callable1 = () -> {

			System.out.println("Returning Hello World");
			return "Hello World";
		};

		Callable<Integer> callable2 = () -> {

			System.out.println("Returning sum of 1 and 1 ");
			return 1 + 1;
		};

		Callable<List<String>> callable3 = () -> {

			List<String> persons = new ArrayList<>();
			persons.add("John");

			persons.add("Martha");

			persons.add("Chembaruthi");

			System.out.println("Returning list of persons");

			return persons;

		};

		Future<String> future1 = service.submit(callable1);
		Future<Integer> future2 = service.submit(callable2);
		Future<List<String>> future3 = service.submit(callable3);

		try {
			System.out.println(future1.get());
			System.out.println(future2.get());
			System.out.println(future3.get());
		} catch (Exception e) {

			e.printStackTrace();
		}

		service.shutdown();
	}

}

Let’s look at the above code in detail:

In the first step in the above code , I am creating an Executor instance using the method Executors.newCachedThreadPool() method. There are several other ways to create the instance. for example by specifying the number of threads you want etc. In this case the method newCachedThreadPool() creates an instance of Thread as and when it is needed and puts them in a cache. Threads which are not used for sixty seconds are removed from the cache.

In the next step I am creating a Callable instance. Callable is an interface just like Runnable interface. Java 5 added Callable interface in addition to Runnable. The difference between them is that Callable interface will return a result whereas Runnable interface will not . So if you want the output of the thread you are executing you need to go for a Callable interface.

A Callable interface has a single method call() declared inside the interface. Remember an interface with a single method is a functional interface . And a functional interface can be replaced by a lamda expression . So instead of creating a new class which implements the Callable interface and overrides the call() method , I am passing the implementation inside call method , as a lamda expression here:

Callable<String> callable1 = () -> {

			System.out.println("Returning Hello World");
			return "Hello World";
		};

I could save the creation of a new class using lamda expression!

The above callable just returns a string.

Similarly I have created two more Callables , one returns an integer and another a list of strings.

After creating them , I am submitting them to the executor service like this:

	Future<String> future1 = service.submit(callable1);
	Future<Integer> future2 = service.submit(callable2);
	Future<List<String>> future3 = service.submit(callable3);

	

submit() method returns a future instance. It will hold the output of the functionality executed by thread once it is available.

You can retrieve the output of a thread from the future object using its get() method:

System.out.println(future1.get());

But remember the get() method is a blocking method . So if you invoke get() method before submitting all the other threads it will block the other threads from starting and you will loose the benefit of concurrency.

For example , if I had invoked future1.get() method above immediately after submitting callable1 to the executor service , the other two threads would not have started :

(Don’t do this):

	Future<String> future1 = service.submit(callable1);
        System.out.println(future1.get());
	Future<Integer> future2 = service.submit(callable2);
	Future<List<String>> future3 = service.submit(callable3);

Retrieve output from future object after all the threads are started.

Finally shut the executor service down:

service.shutdown();

That’s it.

Here is the output on running it the first time:

Returning Hello World
Returning list of persons
Returning sum of 1 and 1
Hello World
2
[John, Martha, Chembaruthi]

And the output on running the second time:

Returning sum of 1 and 1 
Returning Hello World
Returning list of persons
Hello World
2
[John, Martha, Chembaruthi]

As you see you cannot determine the order in which the three threads are executed.

But future.get() method is executed in sequence and hence the final output is displayed in sequence.


Posted

in

,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading