1. 概述
高性能和高可用性是现代软件开发的基本要素之一。
通过非阻塞和异步编程可以实现这一目标。在 Java 中,_CompletableFuture _类提供了一种编写非阻塞代码的方法。但它真的是非阻塞的吗?
在本教程中,我们将检查_ CompletableFuture _在哪些情况下是阻塞的以及在哪些情况下是非阻塞的。
首先,让我们简要介绍一下CompletableFuture类。它是Java 8中作为并发API的一部分引入的一个强大的类。
此外,它实现了 Future 接口,并代表_ _CompletionStage接口的主要实现。因此,它提供了近50种不同的方法来创建和执行异步计算。
为什么我们需要 CompletableFurure 呢?使用 _Future _接口,我们只能通过调用 get() 方法来检索结果。然而,该方法表示阻塞操作。换句话说,它将阻塞当前线程,直到任务的结果可用。
如果我们需要在结果上执行其他操作,我们将得到阻塞操作。
另一方面,由于 CompletionStage,CompletableFuture 提供了链式执行多个并发计算的能力。此功能允许我们创建一个任务链,其中下一个任务在当前任务完成时被触发。
此外,我们可以指定我们得到未来的结果后应该发生什么而不阻塞当前线程。
_CompletableFuture _ 类代表了依赖进程中的阶段,其中一个阶段的完成会触发另一个阶段及其结果。
3. 阻塞 vs. 非阻塞
接下来,让我们了解阻塞和非阻塞处理之间的区别。
在阻塞操作中,调用线程等待另一个线程的操作完成后才继续执行:
这里,任务按顺序执行。_线程1_被_线程2_阻塞。换句话说,_线程1_无法继续执行,直到_线程2_完成其任务处理。
我们可以将阻塞处理视为同步操作。
然而,在我们的系统中阻塞操作可能会导致性能问题,特别是在需要高可用性和可伸缩性的应用程序中。
相比之下,非阻塞操作允许线程在不必等待每个任务完成的情况下同时执行多个计算。
当前线程可以在其他线程并行执行任务的同时继续执行:
在上面的例子中,_线程2_不会阻塞_线程1_的执行。此外,两个线程都在同时运行它们的任务。
除了提高性能外,我们还可以决定在非阻塞操作完成执行后对结果采取什么措施。
4. _CompletableFuture_和非阻塞操作
使用 _CompletableFuture _的主要优点是它可以将多个任务链接在一起,这些任务将在不阻塞当前线程的情况下执行。因此,我们可以说 _CompletableFuture _是非阻塞的。
此外,它提供了多种方法,允许我们以非阻塞的方式执行任务,包括:
- supplyAsync():异步执行任务并返回表示结果的_CompletableFuture_
- thenApply():将函数应用于先前任务的结果,并返回表示转换后结果的_CompletableFuture_
- thenCompose():执行返回_CompletableFuture_的任务,并返回表示嵌套任务结果的_CompletableFuture_
- allOf():并行执行多个任务,并返回表示所有任务完成的_CompletableFuture_
接下来,让我们看一个简单的例子。例如,假设我们有两个我们想以非阻塞方式执行的任务:
CompletableFuture.supplyAsync(() -> "Baeldung")
.thenApply(String::length)
.thenAccept(s -> logger.info(String.valueOf(s)));
任务完成后,它将在标准输出上打印数字 8。
该计算在后台运行并返回一个 future。如果我们有多个依赖的操作,则每个操作由阶段表示。在一个阶段完成后,它会触发其他依赖阶段的计算。
5. 什么时候_CompletableFuture _是阻塞的?
尽管 _CompletableFuture _用于执行非阻塞操作,但在某些场景下仍可能会阻塞当前线程。
在异步通信中,我们通常具有回调机制来检索计算结果。但是,_CompletableFuture _在完成后不会通知我们。
如果需要,我们可以使用 _get() _方法在调用线程中检索结果。
但是,我们需要知道 _get() _方法使用阻塞处理返回结果。 如果需要,它会等待计算完成,然后返回结果。
因此,我们最终会阻塞当前线程,直到未来完成:
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> "Baeldung")
.thenApply(String::toUpperCase);
assertEquals("BAELDUNG", completableFuture.get());
同样,调用_join()_方法也会阻塞当前线程:
CompletableFuture<String> completableFuture = CompletableFuture
.supplyAsync(() -> "Blocking")
.thenApply(s -> s + " Operation")
.thenApply(String::toLowerCase);
assertEquals("blocking operation", completableFuture.join());
这两种方法之间的主要区别是,如果未来异常完成,_join() _方法不会抛出已检查的异常。
此外,我们可以调用 _isDone() _方法,在获取结果之前检查未来是否已完成。
然而,当需要在调用线程中获取计算结果时,我们可以创建_CompletableFuture_,在当前线程中执行其他工作,然后调用_get()_或_join()_方法。通过给它更多的时间,它更有可能在我们获取结果之前完成计算。但仍然不能保证检索不会阻塞线程。
6. 结论
在本文中,我们探讨了 _CompletableFuture _ 是非阻塞的情况以及不是非阻塞的情况。
总之,_CompletableFuture _大多数时间都是非阻塞的。但是,如果我们调用 _get() _ 或 _join() _方法检索结果,它们将阻塞当前线程。
和往常一样,GitHub上的完整源代码都是可用的。
英文原文:https://www.baeldung.com/java-completablefuture-non-blocking
创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。
欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。
https://t.zsxq.com/Z3bAiea 申请时标注来自CSDN。
欢迎加入我们的 slack 工作区,在里面可以对ai 和我进行提问。
https://join.slack.com/t/ai-yx51081/shared_invite/zt-1t8cp1lk3-ZMAFutZcN3PCW~8WQDGjPg