Home > Back-end >  CompletableFuture: Why we need stages at all?
CompletableFuture: Why we need stages at all?

Time:12-07

i did research but didn't find a adequate answer for this question.

Why we need more stages than on stage.

One Thread -> One Big Task(A,B,C,D) VS CompletableFuture with the stages A, B, C, D

So my answer would be the following:

  • If I have more stages, i can split the task over different methods and classes
  • If I have more stages, it's more fair executing the whole task related to other whole tasks. What I mean with that? Let's say we have in our system only one Thread. If I execute it that way -> One Big Task(A,B,C,D), then my next big Task (W,X,Y,Z) get the chance to be executed, after the first big task is ready. With CompletionStages, there it is more fair: because A,W,B,C,X,Y,Z,D could be the execution order

Are there for my last point any metrics/rules, how small I should split the big task into sub-tasks?

  • Is my last point a point for the stages in CompletableFutures?
  • Is my first point a point for the stages in CompletableFutures?
  • Are there other points for using the stages of CompletableFutures?

CodePudding user response:

When you have the choice, like with

CompletableFuture.supplyAsync(() -> method1())
    .thenApply(o1 -> method2(o1))
    .thenApply(o2 -> method3(o2))
    .thenAccept(o3 -> method4(o3));

and

CompletableFuture.runAsync(() -> {
    var o1 = method1();
    var o2 = method2(o1);
    var o3 = method3(o2);
    method4(o3);
});

or

CompletableFuture.runAsync(() -> method4(method3(method2(method1()))));

there is no advantage in using multiple stages. In fact, the first variant is much harder to debug than the alternatives.

Things are different when the chaining does not happen at the same place. Think of a library having a future returning method, encapsulating something like supplyAsync(() -> method1()), another library calling that method, chaining another operation and returning the composition to the application which will chain yet another application.

Expressing the same in a single stage would only be possible when the methods invoked in the functions are still provided by each library’s API and have a sequential nature, i.e. we’re not talking about thenCompose(…) kind of stages.

But such chains are still hard to debug and project Loom is trying to solve this. Then, you’d express the operation as a call sequence, exactly like in the second or third variant even when the methods are potentially blocking, but run it in a virtual thread which will release the underlying native thread each time it would block.

Then, we have even less use for a linear chain of stages.


A remaining use case for creating a linear chain of dependent stages is to have different executors. For example

CompletableFuture.supplyAsync(() -> fetchFromDb(), MY_BACKGROUND_EXECUTOR)
    .thenAcceptAsync(data -> updateSwingModel(data), EventQueue::invokeLater)
    .whenCompleteAsync((x, thrown) ->
         updateStatusBar(jobID, thrown), EventQueue::invokeLater);

here, writing the operation as a single block is not an option…

  • Related