CompletableFuture
在单独的线程(使用线程池)上执行任务,并提供回调函数。假设我在CompletableFuture
中有一个API调用。这是API调用阻止吗?在线程没有收到API响应之前,线程是否会被阻塞? (我知道主线程/ tomcat线程将是非阻塞的,但是执行CompletableFuture任务的线程又如何呢?)
据我所知,Mono完全不受阻碍。
请对此进行说明,如果我错了,请纠正我。
答案 0 :(得分:11)
关于CompletableFuture的一个正确之处是它确实是异步的,它允许您从调用者线程异步运行任务,并且thenXXX
等API允许您在结果可用时对其进行处理。另一方面,CompletableFuture
并不总是非阻塞的。例如,当您运行以下代码时,它将在默认的ForkJoinPool
上异步执行:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
很显然,执行任务的Thread
中的ForkJoinPool
将最终被阻止,这意味着我们不能保证调用不会被阻止。
另一方面,CompletableFuture
公开了API,使您可以使其真正成为非阻塞。
例如,您始终可以执行以下操作:
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;
}
如您所见,CompletableFuture
的API为您提供了complete
和completeExceptionally
方法,可以在需要时完成您的执行,而不会阻塞任何线程。
在上一节中,我们对CF行为进行了概述,但是CompletableFuture和Mono之间的主要区别是什么?
值得一提的是,我们也可以阻止Mono。没有人阻止我们编写以下内容:
Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
当然,一旦我们订阅了将来,调用者线程将被阻止。但是我们始终可以通过提供额外的subscribeOn
运算符来解决此问题。不过,Mono
的更广泛的API并不是未来的关键。
为了理解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;
}
对于CompletableFuture
,一旦方法被调用,它将急切地执行对另一个服务/资源的HTTP调用。即使我们在验证某些前后条件之后并不需要真正的执行结果,它也会开始执行,并且还会为该工作分配额外的CPU / DB连接/What-Ever-Machine-Resources。
相反,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;
}
在这种情况下,只有订阅了最后的mono
之后,任何事情都不会发生。因此,只有当Mono
方法返回的myNonBlockingHttpCallWithMono
被订阅时,才会执行提供给Mono.create(Consumer)
的逻辑。
我们可以走得更远。我们可以使执行更加懒惰。如您所知,Mono
扩展了Publisher
的Reactive Streams规范。 Reactive Streams尖叫的未来是背压支持。因此,使用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);
});
}
});
});
在此示例中,我们仅在订户名为Subscription#request
的情况下才执行数据,因此它声明准备接收数据。
CompletableFuture
是异步的,可以是非阻塞的CompletableFuture
很热心。您不能推迟执行。但是您可以取消它们(总比没有好)Mono
是异步/非阻塞的,并且可以通过将主Thread
与不同的运算符组合在一起,轻松地在不同的Mono
上执行任何调用。Mono
确实很懒,它可以通过订阅者的存在及其准备使用数据来推迟执行启动。答案 1 :(得分:1)
根据 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;
}
然后,以后你只需要做
dispatch.complete(executor);
这将使 CompletableFuture
等同于 Mono
,但我想没有背压。