具有 CompletableFutures 且没有自定义 Executors 的代码是否仅使用等于核心数的线程数?
Does the code with CompletableFutures and no custom Executors use only the number of threads equal to the number of cores?
我正在阅读 java 8 in action,第 11 章(大约 CompletableFuture
s),这让我开始思考我公司的代码库。
行动书中的 java 8 说,如果你有像我在下面写下的代码,你一次只会使用 4 CompletableFuture
s(如果你有一台 4 核计算机) .这意味着如果你想异步执行例如 10 个操作,你将首先 运行 前 4 CompletableFuture
s,然后是第二个 4,然后是剩下的 2 个,因为默认 ForkJoinPool.commonPool()
只提供等于Runtime.getRuntime().availableProcessors()
.
的线程数
在我公司的代码库中,有 @Service
类 称为 AsyncHelper
s,其中包含一个方法 load()
,它使用 CompletableFuture
s 来在单独的块中异步加载有关产品的信息。我想知道他们是否一次只使用 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
秒要加载,那么所有这些20CompletableFuture
不会一次全部执行,一次只执行4个?
我的同事告诉我每个用户将获得 4 个线程,但我认为 JavaDoc 非常清楚地说明了这一点:
public static ForkJoinPool commonPool()
Returns the common pool instance. This pool is statically constructed; its run state is unaffected by attempts to shutdown() or shutdownNow(). However this pool and any ongoing processing are automatically terminated upon program System.exit(int). Any program that relies on asynchronous task processing to complete before program termination should invoke commonPool().awaitQuiescence, before exit.
这意味着我们网站的所有用户只有 1 个池和 4 个线程。
是的,但比那更糟...
公共池的默认大小 1 小于 processors/cores 的数量(如果只有 1 个处理器,则为 1),因此您实际上是在处理3 一次,不是 4.
但是您最大的性能损失是并行流(如果您使用它们),因为它们也使用公共池。流旨在用于超快速处理,因此您不希望它们与繁重的任务共享资源。
如果您有设计为异步的任务(即需要超过几毫秒),那么您应该创建一个池以 运行 它们。这样的池可以静态创建并由所有人重复使用调用线程,这避免了每次使用时创建池的开销。您还应该通过对代码进行压力测试来调整池大小,以找到最佳大小来最大化吞吐量和最小化响应时间。
In my company's code base, there are [...] classes [...] that contain a method load(), that uses CompletableFutures to load information [...]
所以,您是说 load()
方法 等待 I/O 完成?
如果是这样,并且如果@Bohemian 所说的是真的,那么您不应该使用默认线程池。
@Bohemian 说默认池的线程数与主机的 CPU 数大致相同。如果您的应用程序有很多 计算绑定 任务要在后台执行,那就太好了。但如果您的应用程序有很多线程正在等待来自不同网络服务的回复,那就不太好了。那是一个完全不同的故事。
我不是这方面的专家,我不知道如何(除了做实验)找出最佳线程数是多少,但无论那个数字是多少,它都没什么用处理您的系统有多少个 CPU,因此,您不应该为此目的使用默认池。
我正在阅读 java 8 in action,第 11 章(大约 CompletableFuture
s),这让我开始思考我公司的代码库。
行动书中的 java 8 说,如果你有像我在下面写下的代码,你一次只会使用 4 CompletableFuture
s(如果你有一台 4 核计算机) .这意味着如果你想异步执行例如 10 个操作,你将首先 运行 前 4 CompletableFuture
s,然后是第二个 4,然后是剩下的 2 个,因为默认 ForkJoinPool.commonPool()
只提供等于Runtime.getRuntime().availableProcessors()
.
在我公司的代码库中,有 @Service
类 称为 AsyncHelper
s,其中包含一个方法 load()
,它使用 CompletableFuture
s 来在单独的块中异步加载有关产品的信息。我想知道他们是否一次只使用 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
秒要加载,那么所有这些20CompletableFuture
不会一次全部执行,一次只执行4个?
我的同事告诉我每个用户将获得 4 个线程,但我认为 JavaDoc 非常清楚地说明了这一点:
public static ForkJoinPool commonPool() Returns the common pool instance. This pool is statically constructed; its run state is unaffected by attempts to shutdown() or shutdownNow(). However this pool and any ongoing processing are automatically terminated upon program System.exit(int). Any program that relies on asynchronous task processing to complete before program termination should invoke commonPool().awaitQuiescence, before exit.
这意味着我们网站的所有用户只有 1 个池和 4 个线程。
是的,但比那更糟...
公共池的默认大小 1 小于 processors/cores 的数量(如果只有 1 个处理器,则为 1),因此您实际上是在处理3 一次,不是 4.
但是您最大的性能损失是并行流(如果您使用它们),因为它们也使用公共池。流旨在用于超快速处理,因此您不希望它们与繁重的任务共享资源。
如果您有设计为异步的任务(即需要超过几毫秒),那么您应该创建一个池以 运行 它们。这样的池可以静态创建并由所有人重复使用调用线程,这避免了每次使用时创建池的开销。您还应该通过对代码进行压力测试来调整池大小,以找到最佳大小来最大化吞吐量和最小化响应时间。
In my company's code base, there are [...] classes [...] that contain a method load(), that uses CompletableFutures to load information [...]
所以,您是说 load()
方法 等待 I/O 完成?
如果是这样,并且如果@Bohemian 所说的是真的,那么您不应该使用默认线程池。
@Bohemian 说默认池的线程数与主机的 CPU 数大致相同。如果您的应用程序有很多 计算绑定 任务要在后台执行,那就太好了。但如果您的应用程序有很多线程正在等待来自不同网络服务的回复,那就不太好了。那是一个完全不同的故事。
我不是这方面的专家,我不知道如何(除了做实验)找出最佳线程数是多少,但无论那个数字是多少,它都没什么用处理您的系统有多少个 CPU,因此,您不应该为此目的使用默认池。