Java 单一vs完全未来
Java 单一vs完全未来,java,reactive-programming,project-reactor,completable-future,Java,Reactive Programming,Project Reactor,Completable Future,CompletableFuture在单独的线程上执行任务(使用线程池)并提供回调函数。假设我在CompletableFuture中有一个API调用。这是API调用阻塞吗?线程是否会被阻塞,直到它没有得到API的响应?(我知道主线程/tomcat线程是非阻塞的,但是CompletableFuture任务正在执行的线程呢?) 据我所知,单声道是完全无阻塞的 请阐明这一点,如果我错了,请纠正我。CompletableFuture是异步的。但它是非阻塞的吗? CompletableFuture的一个特点
CompletableFuture
在单独的线程上执行任务(使用线程池)并提供回调函数。假设我在CompletableFuture
中有一个API调用。这是API调用阻塞吗?线程是否会被阻塞,直到它没有得到API的响应?(我知道主线程/tomcat线程是非阻塞的,但是CompletableFuture任务正在执行的线程呢?)
据我所知,单声道是完全无阻塞的
请阐明这一点,如果我错了,请纠正我。CompletableFuture是异步的。但它是非阻塞的吗?
CompletableFuture的一个特点是它是真正异步的,它允许您从调用者线程异步运行任务,API(如thenXXX
)允许您在结果可用时处理它。另一方面,CompletableFuture
并不总是非阻塞的。例如,当您运行以下代码时,它将在默认的ForkJoinPool
上异步执行:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
很明显,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
future的API为您提供了complete
和completeeexceptional
方法,它们可以在需要时完成执行,而不会阻塞任何线程
单一vs完全未来
在上一节中,我们对CF行为进行了概述,但是CompletableFuture和Mono之间的主要区别是什么
值得一提的是,我们也可以做单声道阻塞。没有人阻止我们写以下内容:
Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
当然,一旦我们订阅了未来,调用线程就会被阻塞。但是我们可以通过提供一个额外的subscribeOn
操作符来解决这个问题。然而,Mono
更广泛的API并不是关键特性
为了理解CompletableFuture
和Mono
之间的主要区别,让我们回到前面提到的mynonblockingttpcall
方法实现
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连接/任何机器资源
相比之下,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
之前不会发生任何事情。因此,只有当mynonblockingttpcallwithmono
方法返回的Mono
被订阅时,才会执行提供给Mono.create(Consumer)
的逻辑
我们可以走得更远。我们可以让我们的执行更加迟缓。您可能知道,Mono
扩展了反应流规范中的Publisher
。反应流的突出特点是背压支撑。因此,使用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
,可以轻松地在不同的Mono
线程上执行任何调用
确实是懒惰的,它允许订阅者延迟执行启动并准备好使用数据Mono
- 基于Oleh的答案,一个可能的解决方案是
public CompletableFuture-mynonblockingttpcall(CompletableFuture-dispatch,Object-someData){
var uncompletedFuture=new CompletableFuture();//创建未完成的未来
发送。然后接受(x->x.submit(()->{
myAsyncHttpClient.execute(someData,(结果,异常->{
if(异常!=null){
未完成的未来。例外地完成(例外);
返回;
}
未完成的未来。完成(结果);
})
}));
返回未完成的未来;
}
然后,以后你就这么做了
dispatch.complete(执行者);
这将使
CompletableFuture
相当于Mono
,但我想没有背压。谢谢你的详细解释,Oleh。真的很感激。这意味着我的理解是正确的:如果我在CompletableFuture中调用一个api,需要1秒的响应时间,那么ForkJoinPool中的线程最终将被激活被阻止1秒?如果我错了,请纠正我。@ForkJoinPool的XYZ底层机制稍微聪明一点,因此在大量任务的情况下,它可以不阻止而开始在fork中执行另一项工作,但一旦所有工作完成,它将开始加入任务并最终被阻止。但正如我所说的,这取决于底层im客户端的实现。此外,如果在任务发送到工作线程之前请求了任务结果,则ForkJoinTask可能会在当前线程上执行。这意味着如果您将任务提交到池,但直接在句柄上调用get()
,则提交者可能会