CompletableFuture:为啥我们需要阶段?
Posted
技术标签:
【中文标题】CompletableFuture:为啥我们需要阶段?【英文标题】:CompletableFuture: Why we need stages at all?CompletableFuture:为什么我们需要阶段? 【发布时间】:2022-01-08 07:12:47 【问题描述】:我做了研究,但没有找到这个问题的充分答案。
为什么我们需要比舞台更多的舞台。
一个线程 -> 一个大任务(A,B,C,D) VS CompletableFuture 具有阶段 A、B、C、D
所以我的答案如下:
如果我有更多阶段,我可以将任务拆分为不同的方法和类 如果我有更多阶段,执行与其他整体任务相关的整个任务会更公平。我的意思是什么?假设我们的系统中只有一个线程。如果我以这种方式执行它 -> 一个大任务(A,B,C,D),那么在第一个大任务准备好之后,我的下一个大任务(W,X,Y,Z)就有机会被执行。使用 CompletionStages 更公平:因为 A,W,B,C,X,Y,Z,D 可能是执行顺序我的最后一点是否有任何指标/规则,我应该将大任务分成多小的子任务?
我的最后一点是针对 CompletableFutures 中的阶段的吗? 我的第一点是不是 CompletableFutures 中的各个阶段的要点? 使用 CompletableFutures 的阶段还有其他要点吗?【问题讨论】:
假设您有任务 A、B 和 C,其中 A 后面跟着 B 或 C,具体取决于变量 x。现在想象在处理 A 之前没有设置 x。在那里,如果你想在这里异步做事情,你需要阶段。 但是如果我只使用一个 completableFuture 管道,那么在执行 B 和 C 之前计算 x,如果我在一个线程中使用这个“阶段”也是一样的。我没有考虑涉及到复杂的 CompletalbeStages.allOf()/anyOf() 来混合不同的管道;我的意思是一个管道中的正常 thenApplyAsync 方法 我的意思是,您可以同时计算 x 并异步处理 A。然后,根据 x 的结果,将 B 或 C 链接到 A。 是的,我明白,你的意思,不认为我们可以将其添加到我的项目符号中,....但也许还有一些分期和直接分期的原因,所以 cf .thenApplyAsync()..thenApplayAsync()..thenApplyAsync() 在您的情况下,此链将被破坏,并且条件语句将在其中的某个点;那 - 我所说的同意你 - 是使用 CFs 的一个很好的理由^^ 【参考方案1】:当你有选择的时候,比如
CompletableFuture.supplyAsync(() -> method1())
.thenApply(o1 -> method2(o1))
.thenApply(o2 -> method3(o2))
.thenAccept(o3 -> method4(o3));
和
CompletableFuture.runAsync(() ->
var o1 = method1();
var o2 = method2(o1);
var o3 = method3(o2);
method4(o3);
);
或
CompletableFuture.runAsync(() -> method4(method3(method2(method1()))));
使用多个阶段没有任何优势。事实上,第一个变体比其他变体更难调试。
当链接不在同一个地方发生时,情况就不同了。想象一个库有一个未来的返回方法,封装像supplyAsync(() -> method1())
这样的东西,另一个库调用该方法,链接另一个操作并将组合返回给将链接另一个应用程序的应用程序。
只有当函数中调用的方法仍然由每个库的 API 提供并且具有顺序性时,才可能在单个阶段中表达相同的内容,即我们不是在谈论 thenCompose(…)
类型的阶段。
但是这样的链仍然很难调试,而 Loom 项目正试图解决这个问题。然后,您可以将操作表示为调用序列,就像在第二个或第三个变体中一样,即使方法可能会阻塞,但在虚拟线程中运行它,每次阻塞时都会释放底层本机线程。
那么,我们对线性阶段链的使用就更少了。
创建依赖阶段的线性链的剩余用例是具有不同的执行器。例如
CompletableFuture.supplyAsync(() -> fetchFromDb(), MY_BACKGROUND_EXECUTOR)
.thenAcceptAsync(data -> updateSwingModel(data), EventQueue::invokeLater)
.whenCompleteAsync((x, thrown) ->
updateStatusBar(jobID, thrown), EventQueue::invokeLater);
在这里,将操作编写为单个块不是一种选择……
【讨论】:
是的,有不同的执行者,如果忘记了^^,你就在这里.. 是的,Loom 可以改变游戏规则,如果我知道的话,Java 18 将在实验模式下引入 Loom,我猜?直截了当地说:“使用多个阶段没有优势”,我的公平点不是重点吗?如果我把它分成更多的阶段,执行者可以做更多的大任务,如果执行者满了,它不必先完成大任务1、2、3,然后再完成可以从大任务4、5、6开始(3个线程的Executor) 在这种情况下,无法保证执行器会从另一个依赖链中获取任务。事实上,对于默认的执行器,一个 Fork/Join 线程池,这是不太可能的,因为它更喜欢本地排队的任务(即来自其当前执行的依赖链)。如果你想要交错执行,你最好确保池有足够的线程并让抢占式多任务处理完成它的工作。 啊,我明白了,这很好,但如果我只有 cpu 密集型任务,那么我应该选择一个大小与 cpu-core-size 几乎相同的池;如果我采取更多,那么我的表现会更差,因为抢先式多任务处理;是否存在任何其他解决方案,只有 cpu-core 多线程和公平策略? 公平性和最大吞吐量是不同的。如果公平真的是一个问题,不要犹豫,创建比内核更多的线程。是的,切换会降低最大吞吐量,但不要高估影响。其他增加公平性的尝试可能会花费更多而获得更少。以上是关于CompletableFuture:为啥我们需要阶段?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 CompletableFuture.runAsync() 并不总是提交给 ForkJoinPool.commonPool()?