1

I have 3 methods that I need to run in parallel, since they are independent to each other and combine the results of each one at the end and send it as the response. I need to handle exception as well.

In different post I found the below code and modified accordingly.

public Response getResponse() {
    Response resultClass = new Response();
   try {
    CompletableFuture<Optional<ClassA>> classAFuture
        = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture
        = CompletableFuture.supplyAsync(() -> {
             try {
                   return service.getClassB(); 
              }
              catch (Exception e) {
                   throw new CompletionException(e);
              }
     });
    CompletableFuture<ClassC> classCFuture
        = CompletableFuture.supplyAsync(() -> { 
            try {
                return service.getClassC();
            } catch (Exception e) {
                throw new CompletionException(e);
            }
    });

   CompletableFuture<Response> responseFuture =
    CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
         .thenApplyAsync(dummy -> {
            if (classAFuture.join().isPresent() {
               ClassA classA = classAFuture.join();
               classA.setClassB(classBFuture.join());
               classA.setClassC(classCFuture.join());
               response.setClassA(classA)
             }
            return response;
         });
   responseFuture.join();
  } catch (CompletionExecution e) {
    throw e;
  }
  return response;
}

Should the above run correctly in parallel? I see it takes some more time, and I wanted to make sure I am doing it right.

halfer
  • 19,824
  • 17
  • 99
  • 186
Lolly
  • 34,250
  • 42
  • 115
  • 150
  • If you don't want to rely on `CompletableFuture.allOf` you could also do this as describe in this answer: https://www.stackoverflow.com/a/74662100/402428 Basically, write a helper function that can convert a `CompletableFuture>` to a `Stream>`. – michid Dec 08 '22 at 20:15

2 Answers2

1

If you want to run methods in parallel you should use ExecutorService. Try something like that:

ExecutorService myExecutor = Executors.newFixedThreadPool(3);
        List<Future<Object>> futures = myExecutor.invokeAll(
            Arrays.asList(
                () -> service.getClassA(),
                () -> service.getClassB(),
                () -> service.getClassC(),
            )
        );
        myExecutor.shutdown();
1

The idea is correct, but this all could be done with a lot less code:

  public Response getResponse() {
    CompletableFuture<Optional<ClassA>> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA());
    CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB());
    CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC());
    
    try {
      return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
        .thenApply(() -> {
          Response response = new Response();
          Optional<ClassA> maybeA = classAFuture.get();
          if (maybeA.isPresent()) {
            ClassA classA = maybeA.get();
            classA.setClassB(classBFuture.get());
            classA.setClassC(classCFuture.get());
            response.setClassA(classA);
          }
          return response;
        }).get();
    } catch (ExecutionException e) { // Ususally the exception is wrapped to ExecutionException by java concurrency framework itself
        Throwable cause = e.getCause();
        if (cause != null) {
          throw cause;
        } else {
          throw e;
        }
    }
  }

Main things:

  1. You don't need to wrap your exceptions to CompletionException.
  2. You don't need to use thenApplyAsync. Just thenApply is the same thing unless you want to be very specific on the type of thread you want to use. Check this for more information https://stackoverflow.com/a/47489654/3020903
  3. You don't need to join() anything. By the time CompletableFuture.all has finished, you can be very sure that all the supplied jobs have finished and calling get() on then will just return the value.

As for can you be sure jobs A, B and C will be run in parallel. Yes and no. It will be run in parallel if there are enough system resources to run them in parallel. You have done your best to ask them to run in parallel. Maybe at some point you also want to supply your custom thread pool to have more control, but that's a topic for another day.

Tarmo
  • 3,851
  • 2
  • 24
  • 41
  • Thanks for your answer @Tarmo. Methods getClassB() and getClassC() throws error, so if I dont handle it in try catch, it says error unhandled exception. – Lolly May 13 '21 at 19:04