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



我正在阅读Java 8 in Action,第11章(关于CompletableFutures(,它让我想到了我公司的代码库。

Java 8 在行动手册中说,如果你有我在下面写的代码,你一次只能使用 4CompletableFuture(如果你有一台 4 核计算机(。这意味着,如果要异步执行例如 10 个操作,您将首先运行前 4 个CompletableFutures,然后运行第二个 4,然后运行剩余的 2 个,因为默认ForkJoinPool.commonPool()仅提供等于Runtime.getRuntime().availableProcessors()的线程数。

在我公司的代码库中,有@Service个称为AsyncHelpers 的类,其中包含一个方法load(),它使用CompletableFuture在单独的块中异步加载有关产品的信息。我想知道他们是否一次只使用 4 个线程。

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

有一个体系结构决策,即以块的形式加载 pdp 页面的详细信息。加载应该是异步进行的,当前代码使用CompletableFutures。让我们看一下伪代码:

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
}
}

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

这是否意味着,如果该加载方法有 10CompletableFuture秒,并且当前有 2 个人加载 PDP 页面,并且我们总共有 20CompletableFuture要加载,那么所有这 20CompletableFuture不会一次全部执行,而是一次只执行 4 个?

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

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

这意味着我们网站的所有用户只有 1 个池和 4 个线程。

是的,但它比这更糟糕...

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

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

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

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

那么,您是说load()方法等待I/O 完成吗?

如果是这样,并且如果@Bohemian所说的是正确的,那么您不应该使用默认线程池。

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

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

最新更新