Issue
Folks I have been using CompletableFuture in my project and I experienced a strange behaviour. I would like to understand the behaviour. Kindly help
Scenario 1: In the below code the output is I am from supply I am the first thenApply I am the second thenApply .. As expected
public void callAll(){
String calculatedDistance = listBuildComp().stream()
.map(CompletableFuture::join)
.collect(Collectors.joining());
System.out.println(calculatedDistance);
}
private List<CompletableFuture<String>> listBuildComp(){
List<CompletableFuture<String>> result = new ArrayList<>();
result.add(buildComp());
return result;
}
private CompletableFuture<String> buildComp(){
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ")
.thenApply( x -> {
return x.concat(" I am the first thenApply ");
})
.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return workFlowWithServices;
}
Scenario 2: When the below method is changed then the output is I am from supply . Upon further investigation I see that the rest two thenApply runs in their own thread
private CompletableFuture<String> buildComp(){
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ");
workFlowWithServices.thenApply( x -> {
return x.concat(" I am the first thenApply ");
});
workFlowWithServices.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return workFlowWithServices;
}
The reason I am interested with Scenario 2 is imagine you are chaining 2 TASKS then Scenario 1 is okay but imagine you want to chain 50 TASKS then the method will get too big. In this case I wanted to extract each calls in to a method to begin with but eventually extract in to a class if required but I cannot do all these cause of Scenario 2.
Want to know the concept or idea about why scenario 2 behaves in a different way and if there is anyway to make it behave like scenario 1. Kindly share your knowledge. Thank you.
Solution
First of all, you have no guarantee which thread will execute those thenApply
, it could easily be main
.
Then in your example, you build a CompletableFuture
:
CompletableFuture<String> workFlowWithServices =
CompletableFuture.supplyAsync( () -> "I am from supply ");
chain some actions :
workFlowWithServices.thenApply( x -> {
System.out.println("executing");
return x.concat(" I am the first thenApply ");
})
...
but you ignore the result of that thenApply
(which is a CompletableFuture<String>
too). When you join
, you join
on workFlowWithServices
which, when it's done, will return "I am from supply "
. Done. You do not query (you ignore entirely) the result of subsequent actions in thenApply
, thus they do execute, but the result is gone.
I do not get what exactly stops you to build something like this, for example:
private static CompletableFuture<String> buildComp2(){
CompletableFuture<String> one =
CompletableFuture.supplyAsync( () -> "I am from supply ");
CompletableFuture<String> two = one.thenApply( x -> {
System.out.println("executing");
return x.concat(" I am the first thenApply ");
});
CompletableFuture<String> three = two.thenApply( x -> {
return x.concat(" I am the second thenApply ");
});
return three;
}
Answered By - Eugene
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)