You are on page 1of 68

Promises using

CompletableFuture
Outline
Executors and Future

Creating CompletableFuture

Usage Patterns
Executor and Future
Latency Problem

Catalogue PriceFinder ExchangeService Calculate

latency

com.iteratrlearning.examples.promises.pricefinder.PriceCatalogueExample
Blocking
Product product = catalogue.productByName(productName);

Price price = priceFinder.findBestPrice(product);

double exchangeRate

= exchangeService.lookupExchangeRate(USD, localCurrency);

double localPrice = exchange(price, exchangeRate);


Improving Latency

Catalogue PriceFinder

Calculate

ExchangeService

latency
Solutions
1. Thread based approach (1995+)

2. ExecutorService and Future (2005+)

3. CompletableFuture (2014+)
Threads
● You can run two or more tasks concurrently by running multiple threads

● Threads execute a Runnable object

● Runnable can be stateful to return result

Thread t = new Thread(() -> System.out.println("Do some work"));

t.start();

t.join(); // block until thread is done


Limitations of Thread based approach
● Limited set of primitives: Thread.start(), Thread.join(), Runnable

● Coupling of task and thread creation


○ How many tasks are executed concurrently?
○ Priority of tasks

● Runnable doesn’t return a value


Demo

Refactor to use two Threads so the total latency is ~1s.

com.iteratrlearning.examples.promises.pricefinder.PriceCatalogueThread
ExecutorService
...

2. Execute task
1. Submit task

Thread
3. Complete task pool

Executor
Service Working thread
Idle thread
ExecutorService
● Decouples Task Submission from Execution

● Let’s you configure properties including size of pool, queue, keep-alive

● Different ExecutorService for different needs


○ ForkJoinPool : uses work stealing and used by parallel streams
○ ScheduledThreadPoolExecutor : runs tasks periodically or after a delay
○ ThreadPoolExecutor : pooled threads, can be configured
ExecutorService
interface ExecutorService extends Executor {

Future<?> submit(Runnable task);

<T> Future<T> submit(Callable<T> task);

}
A Future is an I-owe-you for a value
Using ExecutorService
ExecutorService executorService

= Executors.newFixedThreadPool(10);

Future<Price> futurePrice

= executorService.submit( () -> priceFinder.findBestPrice(product));

Price price = futurePrice.get();


Put a timeout (ring) on it!
Using ExecutorService
ExecutorService executorService

= Executors.newFixedThreadPool(10);

Future<Price> futurePrice

= executorService.submit( () -> priceFinder.findBestPrice(product));

Price price = futurePrice.get(2, TimeUnit.SECONDS);


Exercise
● Refactor to use Future and ExecutorService

● You should halve the latency (total run ~1s)

com.iteratrlearning.problems.promises.pricefinder.PriceCatalogueFuture
Problems with Future
● How to find the first result of two futures?

● How to composing result of two futures?

● Reason about the flow of data?

● How to “complete” a Future with a result?


ListenableFuture
● Available in Google Guava and supported by Spring

● Let’s you register callback executed when result of Future is available

● Separate utility Futures class provide transformation patterns

● Does not follow “pipeline” pattern


CompletableFuture = Future + Callbacks
Adoption of CompletableFuture
● Java 9’s new HTTP client

● Controllers in Spring 4.2

● Akka
Creating CompletableFuture
Creation patterns
// 1. Instantiate the future

CompletableFuture<Price> future = new CompletableFuture<>();

// 2. Work done on Fork/Join pool

CompletableFuture<Price> price =

CompletableFuture.supplyAsync( () -> priceFinder.findBestPrice(product));


Getting a result
// blocks and throws checked exception

Price price = futurePrice.get();

// default value if not completed

Price price = futurePrice.getNow(Price.FREE);

// blocks and throws unchecked exception

Price price = futurePrice.join();


Callbacks

// Register a callback for when the operation is completed


future.thenAccept(value -> System.out.println(value));

// Or if you don’t care about the value:


future.thenRun(() -> System.out.println("completed"));
CompletableFuture and CompletionStage
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

● Fluent-style stream-like API provided by CompletionStage

● Can be in three states

○ Uncompleted

○ Completed with a value

○ Completed exceptionally
Completing a CompletableFuture

// Instantiate the future

CompletableFuture<Integer> future = new CompletableFuture<>();

// Complete the future

future.complete(42);

future.completeExceptionally(new TimeoutException());
Future, Promise, Deferred
Future vs Promise
● Future lets you read a value at some point in time

● Promise lets you write a value

● You can both read and write to a CompletableFuture


Refactoring practical
Refactor using CompletableFuture creation (supplyAsync) and blocking get patterns (get,
join)

com.iteratrlearning.problems.promises.pricefinder.PriceCatalogueCFSimple
Usage Patterns
Diagram annotations
A -> B: Function<A, B>

(A, B) -> C: BiFunction<A, B, C>

T A CompletableFuture returning a result of type T: CompletableFuture<T>


Transforming CompletableFuture

A -> B
A B
thenApply

CompletableFuture<A> CompletableFuture<B>
Transforming CompletableFuture

// transform a value into another (dual of map)

CompletableFuture<Integer> plusOne

= future.thenApply(x -> x + 1);


Composing CompletableFutures

A -> B
A B
thenCompose

CompletableFuture<A> CompletableFuture<B>
Composing CompletableFutures
// Composing maps a value into a CompletableFuture (dual of flatMap)

future.thenCompose(x -> otherFuture(x));

Use when you have another operation that returns a CompletableFuture


Combining two independent CompletableFutures

A
(A, B) -> C
C

thenCombine
B
Combining two independent CompletableFutures
CompletableFuture<User> futureUser = …;

CompletableFuture<List<Course>> futureCourses = …;

CompletableFuture<LearningPlan> futureLearningPlan

= futureUser. thenCombine(futureCourses, (user, courses) -> { … });


How are CompletableFutures executed?
Same available thread as predecessor

thenCompose
Fork/Join Pool (methods with async suffix)
Thread a thread from
Fork/Join pool

thenComposeAsync
ExecutorService
Thread New thread from
ExecutorService’s thread pool

thenComposeAsync(completableFuture,
executorService)
Refactoring exercise
Refactor to make use of idiomatic CompletableFuture operations.

com.iteratrlearning.problems.promises.pricefinder.PriceCatalogueCFIdiomatic

thenCompose
Catalogue PriceFinder

thenAccept
thenCombine Calculate

ExchangeService
Summary
CompletableFuture Pros
● Async API for combining services

● Similar declarative-style to Streams and Optional API

● Gives a common API that different libraries can all use

● Starting to be supported by popular Java libraries including Spring 4.2


CompletableFuture Cons
● JDK not (yet) retrofitted with CompletableFuture

● Bloated API

● Harder to debug than synchronous code

● No build-in async timeout support (fix in JDK9)


The End
How are CompletableFutures executed?
1. Same available thread as predecessor CompletableFuture or caller thread
a. Potentially better spatial locality
b. Difficult to reason about
2. Fork/Join Pool (methods with async suffix)
a. Global pool used by streams and not customisable
3. ExecutorService (overload of methods with async suffix)
a. Recommended approach

com.iteratrlearning.examples.promises.patterns.CFExecution
Dealing with Exceptions
Exceptions
● When a CompletableFuture completes with Exception then the
downstream CompletionStage is in exception state

● Three methods to handle exceptions


○ exceptionally
■ applies a Function which deals with Throwable and returns a new value
○ handle
■ BiFunction from either value or exception and returns a new value
○ whenComplete
■ Callback wither either value or exception
Exceptions
// Error recovery, dual of orElse() from Optional

priceFuture.exceptionally(ex -> {

// recovery mechanism

// return value

}).thenAccept(value -> { ... });


Exceptions
priceFuture.whenComplete((price, ex) -> {

if(ex == null) {

// value state

} else {

// exception state

});
Refactoring exercise

Refactor to make use of idiomatic CompletableFuture operations.


com.iteratrlearning.problems.promises.pricefinder.PriceCatalogueFaulty

Should print result or “Sorry try again next time: Couldn't connect to the Exchange sorry!”

thenCompose
Catalogue PriceFinder

Calculate
thenCombine

AsyncExchangeService
Sequence Patterns
anyOf

B ? Object

First to complete
C
Anyof
Scenario: query two (or more) services and return the first to complete

CompletableFuture.anyOf(

CompletableFuture.supplyAsync(() -> findGoogleProfilePicture("Richard W")),

CompletableFuture.supplyAsync(() -> findTwitterProfilePicture("Richard W"))

).thenAccept(imgUrl -> System.out.println("Richard’s photo: " + imgUrl));


Allof

B Void

All completed
C
allOf
● Returns a completed CompletableFuture when all CompletableFutures
are completed

● Returns CompletableFuture<Void> :-(

CompletableFuture<Void> bestOffers = CompletableFuture.allOf(

CompletableFuture.supplyAsync(() -> queryBA(“LDN-SF”),

CompletableFuture.supplyAsync(() -> queryVirgin(“LDN-SF”));


Sequence

A A

A A

A A

List<CompletableFuture<A>> CompletableFuture<List<A>>
List<CompletableFuture<T>>
// If any of the of the futures complete exceptionally or are cancelled then the result does

<T> CompletableFuture<List<T>> sequence(List<CompletableFuture<T>> futures) {

CompletableFuture<Void> allFuturesDone =

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

return allFuturesDone.thenApply(v ->

futures.stream()

.map(CompletableFuture::join)

.collect(toList()));

}
Refactoring Exercise
Calculate the total price for a list of products

Execution time should be ~1s

com.iteratrlearning.problems.promises.pricefinder.PriceCatalogueAll
Final Practical (Optional)

Extract first 8 Fetch Each Job Find 10 most Print Summary


Betfair Job List
jobs Page frequent words Info

Refactor to make use of idiomatic CompletableFuture operations and reduce


execution time.

com.iteratrlearning.problems.promises.wrapup.BetfairWrapUp
Implementing non-blocking Timeout
public <T> CompletableFuture<T> timeoutAfter(long timeout, TimeUnit unit) {

CompletableFuture<T> result = new CompletableFuture<T>();

delayer.schedule(() ->

result.completeExceptionally(new TimeoutException()), timeout, unit);

return result;

future.acceptEither(timeoutAfter(1, TimeUnit.SECONDS), value -> { });

You might also like