首页 > 解决方案 > 可完成的未来浪费更多时间?

问题描述

我有一个问题,我正在学习 Java 8 的 CompletableFuture,我用一个运行 Completable 未来的 runAsync 的方法做了一个假人,它是一个简单的 0 到 10 和 o 到 5 的 paralen a 在我运行的第二种方法中0到20也是一样,但是runAsyn的方法比其他方法耗时长,正常吗?

异步方法的持续时间不应该与其他方法相同或更少吗?

这是代码。

public class Sample{

public static void main(String x[]) throws InterruptedException {
    
    runAsync();
    System.out.println("========== SECOND TESTS ==========");
    runSync();
}

static void runAsync() throws InterruptedException {
    long startTimeOne = System.currentTimeMillis();

    CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
        for (int i = 0; i < 10L; i++) {
            System.out.println(" Async One");
        }
    });
    for (int i = 0; i < 5; i++) {
        System.out.println("two");
    }
    System.out.println("It is ready One? (1) " + cf.isDone());
    System.out.println("It is ready One? (2)" + cf.isDone());
    System.out.println("It is ready One? (3)" + cf.isDone());
    System.out.println("It is ready One? (4)" + cf.isDone());
    System.out.println("It is ready One? (5)" + cf.isDone());
    System.out.println("It is ready One? (6)" + cf.isDone());
    long estimatedTimeOne = System.currentTimeMillis() - startTimeOne;
    System.out.println("Total time async: " + estimatedTimeOne);

}

static void runSync() {
    long startTimeTwo = System.currentTimeMillis();

    for (int i = 0; i < 20; i++) {
        System.out.println("No async");
    }
    long estimatedTimeTwo = System.currentTimeMillis() - startTimeTwo;
    System.out.println("Total time no async: " + estimatedTimeTwo);

}

}

正常浪费1 毫秒,runAsync 浪费54 毫秒

这是结果截图

标签: javajava-8completable-future

解决方案


首先,您违反了如何在 Java 中编写正确的微基准测试中提到的基本规则?

最值得注意的是,您在同一运行时中运行这两种方法并允许它们相互影响。

除此之外,您将获得一个消息序列的输出,这显示了您操作的基本问题:您不能同时打印。输出系统本身必须确保打印最终显示出顺序行为。

当你正在执行无法通过并发框架并行运行的操作时,你无法获得性能,你只能增加线程通信开销。

除此之外,操作甚至不一样:

  • 您传递给的动作runAsync用作10L结束边界,换句话说,正在执行long所有其他循环使用的比较int

  • "It is ready One? (6)" + cf.isDone()正在执行两个未出现在顺序变体中的操作。首先,轮询 的状态CompletableFuture,这必须通过线程间语义来完成。其次,它具有字符串连接。两者都是潜在的昂贵操作

  • 异步变体打印 21 条消息,而顺序打印 20 条消息。即使要打印的字符总量在异步操作中也大约多 50%

These points may serve as examples of how easily you can do things wrong in a manual benchmark. But they do not affect the outcome significantly, due to the fundamental aspect mentioned before them. You can’t gain a performance advantage of doing the printing asynchronously at all.

Note that the output is quite consistent in your specific case. Since the common Fork/Join thread pool has not been used before your asynchronous operation, it needs to start a new thread when you submit your job, which takes so long that the subsequent local loop printing "two" completes before the asynchronous operation even starts. The next operation, polling cf.isDone() and performing string concatenation, on the other hand, is so slow, that the asynchronous operation completes entirely before these six print statements complete.

当您将代码更改为

CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Async One");
    }
});
for(int i = 0; i < 10; i++) {
    System.out.println("two");
}
cf.join();

您仍然无法获得性能优势,但性能差异会小得多。当您添加如下语句时

ForkJoinPool.commonPool().execute(() -> System.out.println());

main方法开始时,为了确保线程池不需要在测量方法中初始化,感知的开销甚至可以进一步减少。

此外,您可以交换方法中的runAsync();runSync();方法调用的顺序main,以查看当您在同一个 JVM 中运行这两个方法时,首次执行效果如何影响结果。

这一切还不足以使它成为一个可靠的基准,但应该有助于理解在不理解做微基准的陷阱时会出错的事情。


推荐阅读