Mono与CompletableFuture

时间:2019-02-25 12:37:22

标签: reactive-programming project-reactor

CompletableFuture在单独的线程(使用线程池)上执行任务,并提供回调函数。假设我在CompletableFuture中有一个API调用。这是API调用阻止吗?在线程没有收到API响应之前,线程是否会被阻塞? (我知道主线程/ tomcat线程将是非阻塞的,但是执行CompletableFuture任务的线程又如何呢?)

据我所知,Mono完全不受阻碍。

请对此进行说明,如果我错了,请纠正我。

2 个答案:

答案 0 :(得分:11)

CompletableFuture是异步的。但这是非阻塞的吗?

关于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为您提供了completecompleteExceptionally方法,可以在需要时完成您的执行,而不会阻塞任何线程。

单声道vs CompletableFuture

在上一节中,我们对CF行为进行了概述,但是CompletableFuture和Mono之间的主要区别是什么?

值得一提的是,我们也可以阻止Mono。没有人阻止我们编写以下内容:

Mono.fromCallable(() -> {
    try {
        Thread.sleep(1000);
    }
    catch (InterruptedException e) {

    }

    return 1;
})

当然,一旦我们订阅了将来,调用者线程将被阻止。但是我们始终可以通过提供额外的subscribeOn运算符来解决此问题。不过,Mono的更广泛的API并不是未来的关键。

为了理解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;
}

对于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,但我想没有背压。