Executors in Java

Java 1.5 introduced new classes in the java.util.concurrent package which can greatly simplify multi-threaded programming when several tasks need to be completed, order of execution does not matter but all tasks must be completed before moving on to another step. An extremely basic UML representation of parallel execution of two tasks which must be completed before moving on could look like this:

Simple threading

Prior to Java 1.5, this concept could be implemented with .wait() and .notify()/.notifyAll(), but there always was code that was redundant, which led to may framework approaches and so-called "helper" classes to pull off the concept. It is possible to encounter legacy code from "back in the day", or even new code from developers unaware of the abilities of Executor and related classes, implementing threading in the original way.

It’s not that the wait() and notify() are no longer useful, for long running threads, threads that wake up and do some processing and many other tasks they are still appliciable. The classes in java.util.concurrent can simplify code with a consistent approach when all that is necessary is to do a few things in parallel and block until they all have finished.

In the java.util.concurrent package, there are a handful of classes and interfaces to be aware of to use the Java 1.5 Executors approach to threading. The class is Executors and the interfaces of interest are Callable, Fuiture and the ExecutorService itself.

Before creating the ExecutorService to handle the multi-threaded execution, the Callable and Future interfaces must be discussed. The Future interface is really the best starting point, as it will define the return types of the implementation(s) of Callable that will be given to the ExecutorService created by the Executors factory. Let the creation and use of the ExecutorService simmer on the back burner and first concentrat on the Future and how it works with the Callable object.

To further define the objective of the first example, imagine a use case where two messages or updates are sent and leave the details of sending the messages out of the problem so that the point is to return from both tasks a Boolean to denote success or failure of sending the messages or making the updates. This approach allows the Future to be defined as Future<Boolean>. The most interesting concept with Future is now that Future<Boolean> is defined it will not have to be implemented as the ExecutorService will implement all the plumbing to return the result within the Future<Boolean> object, the implementation of the Callable will be Callable<Boolean> and simply return true or false (thanks to autoboxing, it really will return true or false in this trival example). Now two Callable<Boolean> implementations can be defined.

public class CallableExample1
	implements Callable<Boolean>
{
	public CallableExample1()
	{
		super();
	}
	
	@Override
	public Boolean call()
	{
		// Send the message, make the update, etc.
		return true; // Autoboxing will make this a Boolean, or return Boolean.TRUE for the same effect
	}
}
			

And a second example Callable<Boolean> class, just like the first:

public class CallableExample2
	implements Callable<Boolean>
{
	public CallableExample2()
	{
		super();
	}
	
	@Override
	public Boolean call()
	{
		// Send a message, make the update, etc.  Something different than in CallableExample1
		return true;
	}
}
			

Nothing very exciting exists in either class as they serve only to illustrate the concept not to do anything amazing. With the Callable<Boolean> classes defined, and since the Future<V> is known, the method invoking both Callable<Boolean> can be written.

public class Example
{
	public void putItAllTogether()
	{
		ArrayList<Callable<Boolean>> callables = new ArrayList<Callable<Boolean>>();
		callables.add(new CallableExample1());
		callables.add(new CallableExample2());
		
		ExecutorService executor = Executors.newFixedThreadPool(2);
		try
		{
			List<Future<Boolean>> futures = executor.invokeAll(callables);
			executor.shutdown();
			executor.awaitTermination(1, TimeUnit.MINUTES);
			// Do something interesting with the List<Future<Boolean>> here...
		}
		catch (InterruptedException e);
		{
			// Your favorite error handling
		}
	}
}

The above code clearly leaves out a lot of details that a real production system should implement, such as handling any error condition and doing something useful with the results of the two threads, etc., but these details were ommited intentionally to focus on the usage of the java.util.concurrent package.

The point of the above code is to construct an ExecutorService by using the Executors.newFixedThreadPool() method. Of interest is the parameter 2, for this tells the newFixedThreadPool() to construct an ExecutorService that will execute, at most, two Callable instances at the same time. This is a great feature of the approach, in that it is possible to create the List passed into the invokeAll method with a large number of elements and still only be processing two (or whatever the initialization parameter was) threads at the same time which is extremely useful in many cases such as throttling back how many database connections are used at the same time and other applications. Even when more elements exist in the list than the number allowed to execute at the same time, the same rules hold and the invokeAll method will not return until all of the Callables have completed.

That is the basics of using the ExecutorService and other components withing the java.util.concurrent package. The next step is to do something useful within each Callable, and deal with the results of processing to create useful functionality.