Dan
Dan

Reputation: 1033

Best approach to concurrency issue

I have a Person that I am trying to populate. Many of the setter methods take time to populate since the data is supplied from DB calls that can take some time (over 10 seconds) to return.

I am looking for the best way to populate my mutators asynchronously before returning my Person back to the calling method.

Here's an example:

Person p = service.getPersonById(id);
// populate from DMV DB
p.setTickets(dmvService.getTicketsById(id));  // takes 15 seconds to return data
p.setAccidentRecord(dmvService.getAccidentsById(id));  // takes 10 seconds.
...
...

return p;

I would like to be able to run my methods (setTickets, setAccidentRecord, etc) asynchronously to reduce my load time down from over 25 seconds to around 15.

Upvotes: 1

Views: 70

Answers (1)

John Kugelman
John Kugelman

Reputation: 362037

CompletableFuture, introduced in Java 8, is just the ticket. It is a monadic, chainable class that makes it easy to manage off-thread computations.

A future represents a computation that will finish and yield a result some time in the future. It may have already completed, or it may take a while in some background thread. CompletableFuture builds on this core concept by letting you attach all kinds of additional transformations and handlers to an existing future.

Person p = service.getPersonById(id);

CompletableFuture.allOf(
    CompletableFuture.supplyAsync(() -> dmvService.getTicketsById(id))
                     .thenAccept(p::setTickets),

    CompletableFuture.supplyAsync(() -> dmvService.getAccidentsById(id))
                     .thenAccept(p::setAccidentRecord)
).join();

return p;

In your case, you can use supplyAsync to run getTicketsById and getAccidentsById in background threads. Since you want to call the corresponding setters when those methods return, you use thenAccept to say, "when that method returns, supply the value to this setter".

You have two background computations and you don't want to return until both of them complete. Wrapping both of those futures into a bigger one with allOf gets us there. The allOf future doesn't itself complete until the two inner futures are both completed.

Finally, we join the allOf future, which waits for it to finish before proceeding. If any of this code might throw an exception then join will throw that exception itself—although it'll be wrapped in a CompletionException—so you can add a try/catch around it if you wish.


Unfortunately, Java's first stab at this concept, Future, is an underpowered, lackluster class. They got it right with CompletableFuture, but Future stole the ideal name.

Upvotes: 2

Related Questions