In this blog I will show you the basics of using Java Future and Executor Service. Combining the following:
java.util.concurrent.Future<V>
and
java.util.concurrent.ExecutorService
can be very useful mechanism for solving tasks that have to be computed for a short time and processing the result of calculation further in the logic. In this article I am going to give you an example and some crib notes about using them in concurrent programming.
General Information
Future <V> is an interface that represents the result of an asynchronous computation. Once the computation is finished you can obtain the result of it by using get() method. Bear in mind that this is a blocking operation and waits until the outcome (V) is available.
Calling get() is possible to take considerable amount of time. Instead of wasting time for waiting too long you can apply two approaches. The first one is working with get() as well but setting a timeout value as parameter which will prevent an endless stuck if something goes awry. The second way is by using isDone() method which takes a quick look on the Future and checks if it has finished its work or not.
ExecutorService represents abstraction of threads pool and can be created by the utility methods of Executors class. These methods can initialize a number of handful executors depending on the purpose they will be used for. There are some ways to delegate task to ExecutorService:
–execute(Runnable) – returns void and cannot access the result
–submit(Runnable or Callable<T>) – returns a Future object. The main difference is that when submitting Callable<T> the result can be accessed via the returned Future object
–invokeAny(Collection<Callable<T>>) – returns the result of one of the Callable objects that finished its work successfully. The rest tasks are cancelled.
–invokeAll(Collection<Callable<T>>))Â – returns a list of Future objects. All tasks are executed and the outcome can be obtained via the returned result list.
Last, when all tasks have finished their work, the threads in ExecutorService are still running. They are not destroyed yet and are in a “stand by” mode. This will make the JVM keep running. For the purpose of bypassing this problem Java offers you two methods – shutdown() and shutdownNow(). The key difference between them is stopping of ExecutorService. shutdown() will not stop it immediately and will wait all running threads to finish. Meanwhile ExecutorService will not accept new tasks. On the other hand shutdownNow() will try to stop it immediately. It will try to stop instantly all running tasks and to skip the processing of the waiting ones. The method returns a list of all running tasks for which there are no guarantees when will be stopped.
Code Example
In order to illustrate the written above I have prepared a simple code demo. To start with, we have a class called CalculationTask implementing
Callable <Result>
Callable is an interface which stands for a task that returns a result after some computations. This class contains our business logic and every time a task starts, call() method is executed. In our case it contains calculations which take a lot of time to be completed.
CalculationTask
package eu.dreamix.calculator; import java.util.concurrent.Callable; public class CalculationTask implements Callable<Result> { private final BigDecimal invoiceId; public CalculationTask(BigDecimal invoiceId) { this.invoiceId = invoiceId; } @Override public Result call() throws Exception { Result result = null; ... // Calculations for which it takes a long time be completed ... return result; } }
The next class which implementation I have not shown below, because it is irrelevant for the example is Result. It holds the result type of computation returned by call().
InvoiceCalculatorClient
This is the palace where we create an Executor service with fixed amount of threads. A separate task is created for every element in a list and is submitted it to the executor. Then we get the result of computation with the help of the returned Future object in order to operate with it as we need it.
package eu.dreamix.calculator.bad; import java.math.BigDecimal; 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; import eu.dreamix.calculator.CalculationTask; import eu.dreamix.calculator.Result; public class InvoiceCalculatorClient { public static void main(String[] args) { List<Result> invoiceCalculationsResult = new ArrayList<Result>(); ExecutorService executor = Executors.newFixedThreadPool(THREADS_COUNT); // invoiceIDs is a collection of Invoice IDs for (BigDecimal invoiceID : invoiceIDs) { Callable<Result> task = new CalculationTask(invoiceID); Future<Result> future = executor.submit(task); Result calculationResult = null; try { calculationResult = future.get(); } catch (InterruptedException | ExecutionException e) { // ... Exception handling code ... } invoiceCalculationsResult.add(calculationResult); } executor.shutdown(); } }
If you observe the example carefully, you will see a small issue in the for-loop that can cause a big problem in future. (Hint: get() is a blocking operation). It is obvious that get() method is called right away submitting a task to the executor. This means that the next task will not start its work before previous task finishes and there is no effect of multithreading approach in this case.
... List<Future<Result>> futuresList = new ArrayList<>(); // invoiceIDs is a collection of Invoice IDs for (BigDecimal invoiceID : invoiceIDs) { Callable<Result> task = new CalculationTask(invoiceID); Future<Result> future = executor.submit(task); futuresList.add(future); } for (Future<Result> future : futuresList) { Result calculationResult = null; try { calculationResult = future.get(); } catch (InterruptedException | ExecutionException e) { // ... Exception handling code ... } invoiceCalculationsResult.add(calculationResult); } ...
In the example above the first thing that we do is to start the tasks and to save the Future result. When we reach future.get() it is very likely to have nearly ready result. This time depends on the threads count and the logic of the task. Whether with complex logic or reasonable threads count the fix shows acceptable speed-up.
Another idea for solution is creating a collection of Callables<V> objects and using invokeAll(Collection<Callable>)).
List<Callable<Result>> callables = new ArrayList<>(); callables.add(new CalculationTask(invoiceID)); executorService.invokeAll(callables);
Final words
The combination of Future and ExecutorService is a powerful instrument for background task execution, because its flexibility makes it suitable for:
-Time consuming calculations
-Calling web services or accessing remote services/resources
-Working with large-sized data structures
Feel free to share your experience or to ask questions on the topic in the comments section below.
References:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executors.html
https://www.baeldung.com/java-future
https://www.baeldung.com/java-executor-service-tutorial