Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mono vs CompletableFuture

CompletableFuture executes a task on a separate thread ( uses a thread-pool ) and provides a callback function. Let's say I have an API call in a CompletableFuture. Is that an API call blocking? Would the thread be blocked till it does not get a response from the API? ( I know main thread/tomcat thread will be non-blocking, but what about the thread on which CompletableFuture task is executing? )

Mono is completely non-blocking, as far as I know.

Please shed some light on this and correct me if I am wrong.

like image 790
XYZ Avatar asked Feb 25 '19 12:02

XYZ


People also ask

What is the difference between mono and flux?

A Flux object represents a reactive sequence of 0.. N items, while a Mono object represents a single-value-or-empty (0..1) result. This distinction carries a bit of semantic information into the type, indicating the rough cardinality of the asynchronous processing.

Why do we use CompletableFuture?

CompletableFuture is used for asynchronous programming in Java. Asynchronous programming is a means of writing non-blocking code by running a task on a separate thread than the main application thread and notifying the main thread about its progress, completion or failure.

What is mono in spring reactive?

A Mono<T> is a Reactive Streams Publisher , also augmented with a lot of operators that can be used to generate, transform, orchestrate Mono sequences. It is a specialization of Flux that can emit at most 1 <T> element: a Mono is either valued (complete with element), empty (complete without element) or failed (error).

What is the difference between future and CompletableFuture?

The Future interface doesn't provide a lot of features, we need to get the result of asynchronous computation using the get() method, which is blocked, so there is no scope to run multiple dependent tasks in a non-blocking fashion whereas CompleteFuture class can provide the functionality to chain multiple dependent ...


1 Answers

CompletableFuture is Async. But is it non-blocking?

One which is true about CompletableFuture is that it is truly async, it allows you to run your task asynchronously from the caller thread and the API such as thenXXX allows you to process the result when it becomes available. On the other hand, CompletableFuture is not always non-blocking. For example, when you run the following code, it will be executed asynchronously on the default ForkJoinPool:

CompletableFuture.supplyAsync(() -> {     try {         Thread.sleep(1000);     }     catch (InterruptedException e) {      }      return 1; }); 

It is clear that the Thread in ForkJoinPool that executes the task will be blocked eventually which means that we can't guarantee that the call will be non-blocking.

On the other hand, CompletableFuture exposes API which allows you to make it truly non-blocking.

For example, you can always do the following:

public CompletableFuture myNonBlockingHttpCall(Object someData) {     var uncompletedFuture = new CompletableFuture(); // creates uncompleted future      myAsyncHttpClient.execute(someData, (result, exception -> {         if(exception != null) {             uncompletedFuture.completeExceptionally(exception);             return;         }         uncompletedFuture.complete(result);     })      return uncompletedFuture; } 

As you can see, the API of CompletableFuture future provides you with the complete and completeExceptionally methods that complete your execution whenever it is needed without blocking any thread.

Mono vs CompletableFuture

In the previous section, we got an overview of CF behavior, but what is the central difference between CompletableFuture and Mono?

It worth to mention that we can do blocking Mono as well. No one prevents us from writing the following:

Mono.fromCallable(() -> {     try {         Thread.sleep(1000);     }     catch (InterruptedException e) {      }      return 1; }) 

Of course, once we subscribe to the future, the caller thread will be blocked. But we can always work around that by providing an additional subscribeOn operator. Nevertheless, the broader API of Mono is not the key feature.

In order to understand the main difference between CompletableFuture and Mono, lets back to previously mentioned myNonBlockingHttpCall method implementation.

public CompletableFuture myUpperLevelBusinessLogic() {     var future = myNonBlockingHttpCall();      // ... some code      if (something) {        // oh we don't really need anything, let's just throw an exception        var errorFuture = new CompletableFuture();        errorFuture.completeExceptionally(new RuntimeException());         return errorFuture;     }     return future; } 

In the case of CompletableFuture, once the method is called, it will eagerly execute HTTP call to another service/resource. Even though we will not really need the result of the execution after verifying some pre/post conditions, it starts the execution, and additional CPU/DB-Connections/What-Ever-Machine-Resources will be allocated for this work.

In contrast, the Mono type is lazy by definition:

public Mono myNonBlockingHttpCallWithMono(Object someData) {     return Mono.create(sink -> {             myAsyncHttpClient.execute(someData, (result, exception -> {                 if(exception != null) {                     sink.error(exception);                     return;                 }                 sink.success(result);             })     }); }   public Mono myUpperLevelBusinessLogic() {     var mono = myNonBlockingHttpCallWithMono();      // ... some code      if (something) {        // oh we don't really need anything, let's just throw an exception         return Mono.error(new RuntimeException());     }     return mono; } 

In this case, nothing will happen until the final mono is subscribed. Thus, only when Mono returned by the myNonBlockingHttpCallWithMono method, will be subscribed, the logic provided to Mono.create(Consumer) will be executed.

And we can go even further. We can make our execution much lazier. As you might know, Mono extends Publisher from the Reactive Streams specification. The screaming feature of Reactive Streams is backpressure support. Thus, using the Mono API we can do execution only when the data is really needed, and our subscriber is ready to consume them:

Mono.create(sink -> {     AtomicBoolean once = new AtomicBoolean();     sink.onRequest(__ -> {         if(!once.get() && once.compareAndSet(false, true) {             myAsyncHttpClient.execute(someData, (result, exception -> {                 if(exception != null) {                     sink.error(exception);                     return;                 }                 sink.success(result);             });         }     }); }); 

In this example, we execute data only when subscriber called Subscription#request so by doing that it declared its readiness to receive data.

Summary

  • CompletableFuture is async and can be non-blocking
  • CompletableFuture is eager. You can't postpone the execution. But you can cancel them (which is better than nothing)
  • Mono is async/non-blocking and can easily execute any call on different Thread by composing the main Mono with different operators.
  • Mono is truly lazy and allows postponing execution startup by the subscriber presence and its readiness to consume data.
like image 63
Oleh Dokuka Avatar answered Sep 18 '22 20:09

Oleh Dokuka