[英]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.如果我错了,请对此有所了解并纠正我。
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 为您提供了complete
和completeExceptionally
方法,可以在需要时完成您的执行,而不会阻塞任何线程。
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.为了理解CompletableFuture
和Mono
之间的主要区别,让我们回到前面提到的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
时才执行数据,这样它就声明了它准备好接收数据。
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.