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()
      ,则提交者可能会