Home > Software design >  Has CompletableFuture.allOf() any advantage over a loop with CompletableFuture.join() when just wait
Has CompletableFuture.allOf() any advantage over a loop with CompletableFuture.join() when just wait

Time:11-01

I am making multiple async calls to my database. I store all those async calls on a List<CompletableFuture<X>> list. I want to collect all the results together, so I need to wait for all of those calls to complete.

One way is to create a CompletableFuture.allOf(list.toArray(...))...

Another way is to use: list.stream.map(cf -> cf.join())...

I was just wondering if there are any advantages of creating the global CompletableFuture and waiting for it to complete (when all the individual CompletableFuture complete) over directly waiting for the individual CompletableFutures to complete.

CodePudding user response:

The main thread gets blocked either way.


static CompletableFuture<Void> getFailingCF() {
    return CompletableFuture.runAsync(() -> {
        System.out.println("getFailingCF :: Started getFailingCF.. ");
        throw new RuntimeException("getFailingCF:: Failed");
    });
}

static CompletableFuture<Void> getOkCF() {
    return CompletableFuture.runAsync(() -> {
        System.out.println("getOkCF :: Started getOkCF.. ");
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3));
        System.out.println("getOkCF :: Completed getOkCF.. ");
    });
}

public static void main(String[] args) {
     List<CompletableFuture<Void>> futures = new ArrayList<>();
     futures.add(getFailingCF());
     futures.add(getOkCF());
      
     // using CompletableFuture.allOf
     var allOfCF = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
     
     allOfCF.join();
    
     // invoking join on individual CF
     futures.stream()
            .map(CompletableFuture::join)
            .collect(Collectors.toList());
 }

In the code snippet above, the difference lies in handling exception: The CompletableFuture.allOf(..) wraps any exception thrown by any of the CompletableFutures while allowing rest of the threads (executing the CompletableFuture) continue their execution.

The list.stream.map(cf -> cf.join())... way immediately throws the exception and terminates the app (and all threads executing the CFs in the list).

Note that invoking join() on allOf throws the wrapped exception, too. It will also terminate the app. But, by this time, unlike list.stream.map(cf -> cf.join())..., the rest of the threads have completed their processing.


allOfCF.whenComplete(..) is one of the graceful ways to handle the execution result (normal or exceptional) of all the CFs:


 allOfCF.whenComplete((v, ex) -> {
     System.out.println("In whenComplete...");
     System.out.println("----------- Exception Status ------------");

     System.out.println(" 1: "   futures.get(0).isCompletedExceptionally());
     System.out.println(" 2: "   futures.get(1).isCompletedExceptionally());
 });

In the list.stream.map(cf -> cf.join())... way, one needs to wrap the join() call in try/catch.

  • Related