0

I have a method getCount(id) that returns an integer from a database, as a CompletableFuture. I want to loop through all the ids, and get the result for all of them, and add them to one global int. Then I want to do something with that int:

int totalCount;
for (String id : API.getIDs()) {
    //OtherAPI.getCount(id) will return the CompletableFuture<Integer>
    OtherAPI.getCount(id).thenAccept(count -> totalCount += count);
}

System.out.println("" + totalCount);

This gives an error, because you can't add an integer to another integer in thenAccept, because it's a consumer. What is the way to do this?

stijnb1234
  • 184
  • 4
  • 19
  • What does "won't work" mean? Compiler error? Runtime Exception? Unexpected behaviour? Please [edit] the post and provide the necessary information. – Turing85 Sep 13 '20 at 14:10
  • Added, but I added "won't work" more to make it clear that it can't be done this way. – stijnb1234 Sep 13 '20 at 14:12
  • You cannot change an outer-scope variable in a lambda since the variable must be final or efectively-final wrt. the lambda. You can use an [`AtomicInteger`](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/util/concurrent/atomic/AtomicInteger.html) to avoid this problem. – Turing85 Sep 13 '20 at 14:15
  • Yes, I know, but I don't know how to do it. That is the reason for this question. – stijnb1234 Sep 13 '20 at 14:16
  • 1
    There's a solution for you https://stackoverflow.com/questions/30026824/modifying-local-variable-from-inside-lambda – wisnix Sep 13 '20 at 14:17
  • @wisnix But with a AtomicInteger, it won't wait for all the iterations, is it? – stijnb1234 Sep 13 '20 at 14:19
  • Why wouldn't it wait for all the iterations? Also, why are you using `thenAccept` instead of `get`? While `AtomicInteger` will work it is also an overkill. – wisnix Sep 13 '20 at 14:26
  • Because the code above is an example of what I want to achieve. – stijnb1234 Sep 13 '20 at 14:27

1 Answers1

2

Mind that the function passed to thenAccept can be performed by an arbitrary thread, so thankfully, Java does not allow them to modify a local variable. When you change the variable to a field on the heap, the compiler would accept it, but the result would be entirely broken.

The simplest solution would be

int totalCount = 0;
for(String id: API.getIDs()) {
    totalCount += OtherAPI.getCount(id).join();
}

It just waits for the availability of each value, to sum them locally. Depending on the operations encapsulated by the OtherAPI.getCount(…) calls, this might even be the most efficient solution.

But when these operations take a significant time and can truly run in parallel, i.e. do not depend on a shared resource internally, it might be beneficial not to wait before all operations have been commenced.

You can do this like

CompletableFuture<Integer> result = CompletableFuture.completedFuture(0);
for(String id: API.getIDs()) {
    result = result.thenCombine(OtherAPI.getCount(id), Integer::sum);
}
System.out.println(result.join());

Here, the loop is entirely wait-free. It will schedule the summing operation, to be done when two operations have been completed. Then, only the join call at the end will wait for the final result. At this point, all asynchronous operations have been submitted already.

Holger
  • 285,553
  • 42
  • 434
  • 765