Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Completable Futures allOf different data types

I have 3 CompletableFutures all 3 returning different data types.

I am looking to create a result object that is a composition of the result returned by all the 3 futures.

So my current working code looks like this:

public ClassD getResultClassD() {

    ClassD resultClass = new ClassD();
    CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB() );
    CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC() );

    CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
                     .thenAcceptAsync(it -> {
                        ClassA classA = classAFuture.join();
                        if (classA != null) {
                            resultClass.setClassA(classA);
                        }

                        ClassB classB = classBFuture.join();
                        if (classB != null) {
                            resultClass.setClassB(classB);
                        }

                        ClassC classC = classCFuture.join();
                        if (classC != null) {
                            resultClass.setClassC(classC);
                        }

                     });

    return resultClass;
}

My questions are:

  1. My assumption here is that since I am using allOf and thenAcceptAsync this call will be non blocking. Is my understanding right ?

  2. Is this the right way to deal with multiple futures returning different result types ?

  3. Is it right to construct ClassD object within thenAcceptAsync ?

  4. Is it appropriate to use the join or getNow method in the thenAcceptAsync lambda ?
like image 322
Anand Sunderraman Avatar asked Feb 27 '17 15:02

Anand Sunderraman


People also ask

How does CompletableFuture allOf work?

It returns a new CompletableFuture object when all of the specified CompletableFutures are complete. If any of the specified CompletableFutures are complete with an exception, the resulting CompletableFuture does as well, with a CompletionException as the cause.

Is CompletableFuture get blocking?

It just provides a get() method which blocks until the result is available to the main thread. Ultimately, it restricts users from applying any further action on the result. You can create an asynchronous workflow with CompletableFuture. It allows chaining multiple APIs, sending ones to result to another.

How Future is different from CompletableFuture?

Future vs CompletableFuture. CompletableFuture is an extension to Java's Future API which was introduced in Java 5. A Future is used as a reference to the result of an asynchronous computation.

How do you use Executorservice with CompletableFuture?

The above code block shows how we can use the Executor in CompletableFuture. We create the Executor Service object at line 7 with thread pool as fixed thread pool with 2 as value. As a next step in line 20, we just simply provide it in the runAsync() method as a parameter of CompletableFuture class.


1 Answers

Your attempt is going into the right direction, but not correct. Your method getResultClassD() returns an already instantiated object of type ClassD on which an arbitrary thread will call modifying methods, without the caller of getResultClassD() noticing. This can cause race conditions, if the modifying methods are not thread safe on their own, further, the caller will never know, when the ClassD instance is actually ready for use.

A correct solution would be:

public CompletableFuture<ClassD> getResultClassD() {

    CompletableFuture<ClassA> classAFuture
        = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture
        = CompletableFuture.supplyAsync(() -> service.getClassB() );
    CompletableFuture<ClassC> classCFuture
        = CompletableFuture.supplyAsync(() -> service.getClassC() );

    return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture)
         .thenApplyAsync(dummy -> {
            ClassD resultClass = new ClassD();

            ClassA classA = classAFuture.join();
            if (classA != null) {
                resultClass.setClassA(classA);
            }

            ClassB classB = classBFuture.join();
            if (classB != null) {
                resultClass.setClassB(classB);
            }

            ClassC classC = classCFuture.join();
            if (classC != null) {
                resultClass.setClassC(classC);
            }

            return resultClass;
         });
}

Now, the caller of getResultClassD() can use the returned CompletableFuture to query the progress state or chain dependent actions or use join() to retrieve the result, once the operation is completed.

To address the other questions, yes, this operation is asynchronous and the use of join() within the lambda expressions is appropriate. join was exactly created because Future.get(), which is declared to throw checked exceptions, makes the use within these lambda expressions unnecessarily hard.

Note that the null tests are only useful, if these service.getClassX() can actually return null. If one of the service calls fails with an exception, the entire operation (represented by CompletableFuture<ClassD>) will complete exceptionally.

like image 80
Holger Avatar answered Jan 01 '23 21:01

Holger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!