简体   繁体   English

Mono vs CompletableFuture

[英]Mono vs CompletableFuture

CompletableFuture executes a task on a separate thread ( uses a thread-pool ) and provides a callback function. CompletableFuture在单独的线程上执行任务(使用线程池)并提供回调函数。 Let's say I have an API call in a CompletableFuture .假设我在CompletableFuture有一个 API 调用。 Is that an API call blocking?那是 API 调用阻塞吗? Would the thread be blocked till it does not get a response from the API?线程是否会被阻塞,直到它没有从 API 得到响应? ( I know main thread/tomcat thread will be non-blocking, but what about the thread on which CompletableFuture task is executing? ) (我知道主线程/tomcat 线程将是非阻塞的,但是执行 CompletableFuture 任务的线程呢?)

Mono is completely non-blocking, as far as I know.据我所知,Mono 是完全非阻塞的。

Please shed some light on this and correct me if I am wrong.如果我错了,请对此有所了解并纠正我。

CompletableFuture is Async. CompletableFuture 是异步的。 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. CompletableFuture 的一个优点是它是真正的异步,它允许您从调用者线程异步运行您的任务,并且诸如thenXXX之类的 API 允许您在结果可用时对其进行处理。 On the other hand, CompletableFuture is not always non-blocking.另一方面, CompletableFuture并不总是非阻塞的。 For example, when you run the following code, it will be executed asynchronously on the default ForkJoinPool :例如,当您运行以下代码时,它将在默认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.很明显,执行任务的ForkJoinPool中的Thread最终会被阻塞,这意味着我们不能保证调用是非阻塞的。

On the other hand, CompletableFuture exposes API which allows you to make it truly non-blocking.另一方面, CompletableFuture公开了允许您使其真正非阻塞的 API。

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.如您所见, CompletableFuture未来的 API 为您提供了completecompleteExceptionally方法,可以在需要时完成您的执行,而不会阻塞任何线程。

Mono vs CompletableFuture Mono vs CompletableFuture

In the previous section, we got an overview of CF behavior, but what is the central difference between CompletableFuture and Mono?在上一节中,我们概述了 CF 行为,但 CompletableFuture 和 Mono 之间的主要区别是什么?

It worth to mention that we can do blocking Mono as well.值得一提的是,我们也可以对 Mono 进行阻塞。 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.当然,一旦我们订阅了future,调用者线程就会被阻塞。 But we can always work around that by providing an additional subscribeOn operator.但是我们总是可以通过提供一个额外的subscribeOn操作符来解决这个问题。 Nevertheless, the broader API of Mono is not the key feature.尽管如此, Mono更广泛的 API 并不是关键特性。

In order to understand the main difference between CompletableFuture and Mono , lets back to previously mentioned myNonBlockingHttpCall method implementation.为了理解CompletableFutureMono之间的主要区别,让我们回到前面提到的myNonBlockingHttpCall方法实现。

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.CompletableFuture的情况下,一旦调用该方法,它将急切地执行对另一个服务/资源的 HTTP 调用。 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.即使我们在验证了一些前置/后置条件后实际上不需要执行的结果,它也会开始执行,并且会为此工作分配额外的 CPU/DB-Connections/What-Ever-Machine-Resources。

In contrast, the Mono type is lazy by definition:相比之下, Mono类型根据定义是惰性的:

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.在这种情况下,在订阅最终mono之前不会发生任何事情。 Thus, only when Mono returned by the myNonBlockingHttpCallWithMono method, will be subscribed, the logic provided to Mono.create(Consumer) will be executed.因此,只有当myNonBlockingHttpCallWithMono方法返回的Mono被订阅时,提供给Mono.create(Consumer)的逻辑才会被执行。

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.您可能知道, Mono从 Reactive Streams 规范扩展了Publisher The screaming feature of Reactive Streams is backpressure support. Reactive Streams 的一大特色是背压支持。 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 API,我们只能在真正需要数据时执行,并且我们的订阅者已准备好使用它们:

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.在这个例子中,我们只有在订阅者调用Subscription#request时才执行数据,这样它就声明了它准备好接收数据。

Summary概括

  • CompletableFuture is async and can be non-blocking CompletableFuture是异步的,可以是非阻塞的
  • CompletableFuture is eager. CompletableFuture是急切的。 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是异步/非阻塞的,通过将主Mono与不同的运算符组合在一起,可以轻松地在不同的Thread上执行任何调用。
  • Mono is truly lazy and allows postponing execution startup by the subscriber presence and its readiness to consume data. Mono是真正的懒惰,并允许通过订阅者的存在及其准备使用数据来推迟执行启动。

Building up on Oleh's answer, a possible lazy solution for CompletableFuture would be基于 Oleh 的回答, CompletableFuture一个可能的懒惰解决方案是

public CompletableFuture myNonBlockingHttpCall(CompletableFuture<ExecutorService> dispatch, Object someData) {
    var uncompletedFuture = new CompletableFuture(); // creates uncompleted future

    dispatch.thenAccept(x -> x.submit(() -> {
        myAsyncHttpClient.execute(someData, (result, exception -> {
            if(exception != null) {
                uncompletedFuture.completeExceptionally(exception);
                return;
            }
            uncompletedFuture.complete(result);
        })
    }));

    return uncompletedFuture;
}

Then, later on you simply do然后,以后你只需要做

dispatch.complete(executor);

That would make CompletableFuture equivalent to Mono , but without backpressure, I guess.这将使CompletableFuture等同于Mono ,但我猜没有背压。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM