To understand this better, firstly we must understand what is blocking and why is it bad for our software.

BLOCKING – A blocking/long-running call occurs when a thread is tied up for long periods of time performing computations or waiting for resources. This may be anything – a database call, file I/O, serialization/deserialization of objects, network I/O etc.

There are multiple reasons why blocking negatively affects our code.

  1. During this period the threads, memory and other resources will not be released for use by other processes.
  2. Code following the blocking call will not be executed until a result from the blocked code arrives.
  3. Blocking is a potential bottleneck. It limits an application’s ability to scale.

But how do we solve the problem of blocking?

Blocking is inevitable in most systems. To maintain performance and scalability, we can isolate the blocking operations using Java Futures.

**JAVA FUTURES – **These allow us to isolate the blocking operations to a separate thread so that the execution of the main thread continues uninterrupted. The result of the futures is handled through a callback.

Futures represent the promise of value. In Java 8, the promise of values is represented by a CompletableFuture.

A CompletableFuture eventually resolves into one of the following 2 things –

  1. The value of the future

  2. An exception that occurs while resolving the value of the future.

Syntax of a CompletableFuture is as below-

CompletableFuture<Order> futureOrder = CompletableFuture.supplyAsync(() -> new Order(..));

This future takes in a lambda expression that takes some parameters. The lambda body contains all the work that is to be done in the blocking call. When the work is done, the future returns the value (in this case the order object) back to the calling code. All this work is done in a separate thread so that the normal flow of the program is not blocked.

Best Practices in Java 8 Futures:

Now, we will talk about some of the scenarios regarding futures & best practices we can leverage to our benefit.

  1. Using .get & .join to get the value of the future is not a good practice as these are blocking operations. These operations force the current thread to wait for the future to complete before moving on.

Best Practice: Rather than waiting for a future to complete, we can use transformations or callbacks to handle the result of the future. The .thenApply function transforms the value of the future using the lambda provided.

CompletableFuture<String> futureString = futureOrder.thenApply((order) -> order.toString());

2. Sometimes, if a lambda returns a future we can get a nested future.

_Best Practice: _We can use .thenCompose function to flatten the nested future instead of .thenApply. This returns a CompletableFuture instead of a CompletableFuture<CompletableFuture>.

CompletableFuture<Order> flattenedFutureOrder = futureOrder.thenCompose((order) -> completedFuture(order));

3. Futures are executed in separate threads or thread pool. Management of these threads is handled by an Executor or Executor Service. When no Executor is provided for a future, a default thread pool is used. This is convenient but also means that all operations, even the fast ones are competing for the same threads.

#functional programming #future #java #scala #tech blogs #futures #java 8

Java8 Futures: Introduction & Best Practices
1.25 GEEK