Getting a result from a parallel task in Java
September 18, 2016 Leave a comment
In this post we saw how to execute a task on a different thread in Java. The examples demonstrated how to start a thread in the background without the main thread waiting for a result. This strategy is called fire-and-forget and is ideal in cases where the task has no return value.
However, that’s not always the case. What if we want to wait for the task to finish and return a result? Welcome to the future… or to the Future with a capital F.
Imagine that we have the following interface to implement various heavy calculations:
public interface CalculationService { public int calculate(int first, int second); }
…and we have the following implementations:
public class AdditionService implements CalculationService { @Override public int calculate(int first, int second) { try { Thread.sleep(1000); } catch (InterruptedException ex) { //ignore } return first + second; } } public class SubtractionService implements CalculationService { @Override public int calculate(int first, int second) { try { Thread.sleep(2000); } catch (InterruptedException ex) { //ignore } return first - second; } } public class MultiplicationService implements CalculationService { @Override public int calculate(int first, int second) { try { Thread.sleep(3000); } catch (InterruptedException ex) { //ignore } return first * second; } } public class DivisionService implements CalculationService { @Override public int calculate(int first, int second) { try { Thread.sleep(4000); } catch (InterruptedException ex) { //ignore } return first / second; } }
The sleeping threads are meant to simulate that a long running calculation produces the results.
If we want to get the result from all 4 services then we can obviously call them one by one at first:
CalculationService adder = new AdditionService(); CalculationService subtractor = new SubtractionService(); CalculationService multiplier = new MultiplicationService(); CalculationService divider = new DivisionService(); int firstOperand = 10; int secondOperand = 5; //all on single thread Instant start = Instant.now(); int addResult = adder.calculate(firstOperand, secondOperand); int subtractResult = subtractor.calculate(addResult, secondOperand); int multplResult = multiplier.calculate(addResult, secondOperand); int divResult = divider.calculate(addResult, secondOperand); Instant finish = Instant.now(); Duration duration = Duration.between(start, finish); long seconds = duration.getSeconds();
As expected it takes 10 seconds to run all calculations. However, we can do a lot better. We can let all 4 operations execute on their own threads which will run in parallel. Then we wait for all of them to return their results. We’ll reuse what we learnt about the ExecutorService in the previous post.
One way to solve this problem is to implement the Callable of T interface as follows:
public class CalculationServiceTask implements Callable<Integer> { private final CalculationService calculationService; private final int firstOperand; private final int secondOperand; public CalculationServiceTask(CalculationService calculationService, int firstOperand, int secondOperand) { this.calculationService = calculationService; this.firstOperand = firstOperand; this.secondOperand = secondOperand; } @Override public Integer call() throws Exception { return calculationService.calculate(firstOperand, secondOperand); } }
The T type parameter declares the return type. Callable has a single function called call() where we implement what and how to return. The submit() method of the executor service can accept a single Callable and return a Future of T where the Future object holds the result of the operation. In our case though it makes more sense to run the invokeAll method which accepts a collection of Callables and let them execute in parallel. It returns a collection of Future objects. We can then iterate this collection and get each result one by one:
List<Callable<Integer>> calculationTasks = new ArrayList<>(); calculationTasks.add(new CalculationServiceTask(adder, firstOperand, secondOperand)); calculationTasks.add(new CalculationServiceTask(subtractor, firstOperand, secondOperand)); calculationTasks.add(new CalculationServiceTask(multiplier, firstOperand, secondOperand)); calculationTasks.add(new CalculationServiceTask(divider, firstOperand, secondOperand)); ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); try { List<Future<Integer>> invokeAll = newCachedThreadPool.invokeAll(calculationTasks); for (Future<Integer> future : invokeAll) { int result = future.get(); System.out.println(result); } } catch (InterruptedException | ExecutionException ex) { System.err.println(ex.getMessage()); } Instant finish = Instant.now(); Duration duration = Duration.between(start, finish); long seconds = duration.getSeconds();
This time it took 4 seconds to complete all tasks. That corresponds to the longest running task, i.e. division.
Like we saw with runnables in the previous post we can simplify our code with Lambdas in Java 8:
calculationTasks.add(() -> adder.calculate(firstOperand, secondOperand)); calculationTasks.add(() -> subtractor.calculate(firstOperand, secondOperand)); calculationTasks.add(() -> multiplier.calculate(firstOperand, secondOperand)); calculationTasks.add(() -> divider.calculate(firstOperand, secondOperand));
In that case we don’t need to implement the Callable interface at all.
View all posts related to Java here.