CompletableFuture 需要更多时间 - Java 8

CompletableFuture takes more time - Java 8

我有两段代码在技术上是相同的,但第二段比第一段多花 1 秒。第一个在 6 秒内执行,第二个在 7 秒内执行。

Double yearlyEarnings = employmentService.getYearlyEarningForUserWithEmployer(userId, emp.getId());

CompletableFuture<Double> earlyEarningsInHomeCountryCF = currencyConvCF.thenApplyAsync(currencyConv -> {
  return currencyConv * yearlyEarnings;
});

上一个用6s,下一个用7s Here is the link to code

CompletableFuture<Double> earlyEarningsInHomeCountryCF = currencyConvCF.thenApplyAsync(currencyConv -> {
       Double yearlyEarnings = employmentService.getYearlyEarningForUserWithEmployer(userId, emp.getId());
       return currencyConv * yearlyEarnings;  
 });

请解释为什么第二个代码比第一个代码总是多花 1 秒(额外时间)

下面是方法 getYearlyEarningForUserWithEmployer 的签名。只是分享,应该不会有什么影响

Double getYearlyEarningForUserWithEmployer(long userId, long employerId);

Here is the link to code

你的问题非常不完整,但据我们猜测,如果我们假设 currencyConvCF 代表一个可能 运行ning 的异步操作,那么第二种变体需要更长的时间是完全合理的同时执行您的代码片段并且您正在谈论完成所有操作所需的总时间,包括由 thenApplyAsync (earlyEarningsInHomeCountryCF) 返回的 CompletableFuture 表示的操作。

在第一个变体中,您正在调用 getYearlyEarningForUserWithEmployer,而 currencyConvCF 表示的操作可能仍然 运行ning 并发。当两个操作都完成时,将发生乘法。

在第二个变体中,getYearlyEarningForUserWithEmployer 调用是传递给 currencyConvCF.thenApplyAsync 的操作的一部分,因此它不会在 currencyConvCF 表示的操作完成之前开始,所以没有操作会 运行 并发。如果我们假设 getYearlyEarningForUserWithEmployer 需要很长时间,比如一秒钟,并且与其他操作没有内部依赖性,那么当整个操作在该变体中花费更长的时间就不足为奇了。

看来,你真正想做的是这样的:

CompletableFuture<Double> earlyEarningsInHomeCountryCF = currencyConvCF.thenCombineAsync(
    CompletableFuture.supplyAsync(
        () -> employmentService.getYearlyEarningForUserWithEmployer(userId, emp.getId())),
    (currencyConv, yearlyEarnings) -> currencyConv * yearlyEarnings);

因此 getYearlyEarningForUserWithEmployer 未在启动线程中按顺序执行,但两个源操作可以 运行 在最终乘法应用之前异步执行。

但是,当您随后在启动线程中调用 get 时,就像在 github 上的链接代码中一样,第二个操作的异步处理没有任何好处。您的启动线程无需等待完成,而是可以像您的问题的第二个代码变体一样执行独立操作,并且当您不为像单个乘法这样简单的事情生成异步操作时,您可能会更快,即使用相反:

CompletableFuture<Double> currencyConvCF = /* a true asynchronous operation */
return employmentService.getYearlyEarningForUserWithEmployer(userId, emp.getId())
     * employerCurrencyCF.join();

Holger 所说的确实有道理,但在我发布的问题中却没有。我同意这个问题不是以最好的方式写的。

问题是期货的写入顺序导致时间持续增加。

理想情况下,只要代码以正确的反应方式编写,未来的顺序就无关紧要

问题的原因是 Java 的默认 ForkJoinPool 和 Java 默认使用此池 运行 所有 CompletableFutures。如果我 运行 所有具有自定义池的 CompletableFutues,我得到几乎相同的时间,而不管 future 语句的写入顺序如何。

我仍然需要找出 ForkJoinPool 的局限性,并找出为什么我的 20 线程自定义池性能更好。

当我找到正确的原因时,我会更新我的答案。