Patterns and Antipatterns for Java 8 Futures

Richard Whaling

Spantree Technology Group LLC

Java 8 Futures are Great!

  • About 50 extremely powerful methods
  • But how do we use them?
  • Distributed systems are still hard
  • More capability != more safety

Big Balls of Mud

A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. These systems show unmistakable signs of unregulated growth, and repeated, expedient repair. Information is shared promiscuously among distant elements of the system, often to the point where nearly all the important information becomes global or duplicated. The overall structure of the system may never have been well defined. If it was, it may have eroded beyond recognition. Programmers with a shred of architectural sensibility shun these quagmires. Only those who are unconcerned about architecture, and, perhaps, are comfortable with the inertia of the day-to-day chore of patching the holes in these failing dikes, are content to work on such systems.

 

— Brian Foote and Joseph Yoder, Big Ball of Mud.

(Synchronous) Balls of Mud

  • "Spaghetti Code"
  • Synchronous method calls
  • Nightmare to maintain...
  • But it can still be effective.
    • In the most brutish sense

(Concurrent) Balls of Mud

  • Deadlocks are a real problem
  • Testing is really nasty
  • Production issues are often impossible to reproduce
  • An order of magnitude more dangerous and error prone than the synchronous variety
  • Exhibit "spookiness"

We Need Patterns!

Asynchronous Patterns:

Asynchronous Patterns:

Request-Response

    CompletableFuture.supply() [create]
    CompletableFuture.run() [create]
    CompletableFuture.get() [receive]
    CompletableFuture.join() [receive]

 

Pipeline/Filter

    CompletableFuture.thenCompose()

CompletableFuture.handle()
    CompletableFuture.thenApply()
    CompletableFuture.thenAccept()
    CompletableFuture.thenRun()

 

Variation: sub-cycle

    CompletableFuture.run().thenRun().get()

 

Scatter-Gather

    CompletableFuture.applyToEither
    CompletableFuture.acceptEither
    CompletableFuture.runAfterEither
    CompletableFuture.anyOf() [returns Object]

Aggregator

    CompletableFuture.allOf() [returns void]
    CompletableFuture.thenAcceptBoth()
    CompletableFuture.runAfterBoth()

That's (1/3 of) It!

Most CompletableFuture methods come in 3 varieties:

 

thenAccept(Consumer<? super T> action)  thenAcceptAsync(Consumer<? super T> action) thenAcceptAsync(Consumer<? super T> action, Executor executor)

 

These affect when and where the action will be executed.

Applying Patterns

Antipatterns!

  • Only create long cycles from the top level
    • Don't create futures in the body of other futures
    • Forward-flow is always best
    • Otherwise aim for one long cycle
  • Be careful blocking on the main thread
  • Be careful blocking threads on the system Executor!
  • If you might block, as a courtesy, create an Executor

Conclusions:

  • Treat concurrent systems like distributed systems
  • Work from a pattern, your systems will thank you
  • If you can't draw it, redesign it!

 

Thanks!

Patterns and Antipatterns for Futures

By Richard Whaling

Patterns and Antipatterns for Futures

  • 1,097