具有CompletableFutures且没有自定义执行程序的代码是否仅使用等于核心数的线程数?

时间:2018-06-10 20:15:27

标签: java multithreading java-8 future completable-future

我正在阅读第8章(关于CompletableFuture s)中的java 8,它让我思考了我公司的代码库。

java 8 in action book中说如果你有类似我下面写的代码,你一次只能使用4 CompletableFuture个(如果你有4核计算机)。这意味着如果你想异步执行10个操作,你将首先运行前4个CompletableFuture,然后是第2个4,然后是剩下的2个,因为默认ForkJoinPool.commonPool()仅提供线程数等于Runtime.getRuntime().availableProcessors()

在我公司的代码库中,有@Service个名为AsyncHelper的类,其中包含方法load(),使用CompletableFuture来加载信息关于一个产品在不同的块中异步。我想知道他们一次只使用4个线程。

我公司的代码库中有几个这样的异步助手,例如,一个用于产品列表页面(PLP),一个用于产品详细信息页面(PDP)。产品详细信息页面是专门针对特定产品的页面,显示其详细特征,交叉销售产品,类似产品以及更多内容。

有一个架构决定以块的形式加载pdp页面的细节。加载应该是异步发生的,当前代码使用CompletableFuture s。让我们来看看伪代码:

static PdpDto load(String productId) {
    CompletableFuture<Details> photoFuture =
            CompletableFuture.supplyAsync(() -> loadPhotoDetails(productId));
    CompletableFuture<Details> characteristicsFuture =
            CompletableFuture.supplyAsync(() -> loadCharacteristics(productId));
    CompletableFuture<Details> variations =
            CompletableFuture.supplyAsync(() -> loadVariations(productId));

    // ... many more futures

    try {
        return new PdpDto( // construct Dto that will combine all Details objects into one
                photoFuture.get(),
                characteristicsFuture.get(),
                variations.get(),
                // .. many more future.get()s
        );
    } catch (ExecutionException|InterruptedException e) {
        return new PdpDto(); // something went wrong, return an empty DTO
    }
}

如您所见,上面的代码不使用自定义执行程序。

这是否意味着如果该加载方法有10个CompletableFuture并且当前有2个人加载了PDP页面,并且我们总共加载了20个CompletableFuture,那么这20个{{ {1}}不能一次执行,但一次只能执行4次?

我的同事告诉我每个用户将获得4个帖子,但我认为JavaDoc非常清楚地说明了这一点:

  

public static ForkJoinPool commonPool()   返回公共池实例。这个游泳池是静态建造的;其运行状态不受尝试shutdown()或shutdownNow()的影响。但是,在程序System.exit(int)中,此池和任何正在进行的处理将自动终止。在程序终止之前依赖异步任务处理完成的任何程序都应该在退出之前调用commonPool()。awaitQuiescence。

这意味着我们网站的所有用户只有1个包含4个主题的池。

2 个答案:

答案 0 :(得分:7)

是的,但它比那更糟......

公共池的默认大小 1小于处理器/核心数(如果只有1个处理器,则为1),因此您实际上正在处理 3 一次,而不是4。

但是你最大的性能影响是并行流(如果你使用它们),因为它们也使用公共池。 Streams旨在用于超快速处理,因此您不希望它们与繁重的任务共享资源。

如果您的任务设计为异步(即花费超过几毫秒),那么您应该创建一个池来运行它们。这样的池可以静态创建并由所有调用线程重用,这可以避免开销每次使用的池创建。您还应该通过对代码进行压力测试来调整池大小,以找到最佳大小以最大化吞吐量并最大限度地缩短响应时间。

答案 1 :(得分:4)

  

在我公司的代码库中,有[...]类[...]包含方法load(),它使用CompletableFutures来加载信息[...]

那么,您是否说//JButtonArray is an array of JButton objects for(int i=0; i < JButtonArray.length; i++) { r = randInt(1,50); JButtonArray[i].setText(Integer.toString(r)); } 方法等待以完成I / O?

如果是这样,如果@Bohemian说的是真的,那么你就不应该使用默认的线程池。

@Bohemian说默认池的线程数与主机拥有的CPU大致相同。如果您的应用程序有很多计算绑定任务要在后台执行,那就太棒了。但是,如果您的应用程序有很多线程正在等待来自不同网络服务的回复,那就没那么好了。这是一个完全不同的故事。

我不是这个主题的专家,我不知道(除了做实验)如何找出最好的线程数,但不管这个数字是多少,它都会去与您的系统有多少CPU无关,因此,您不应该为此目的使用默认池。