9.3 调整线程池大小
问题
用户希望在通用线程池中自定义线程数量,而不是使用默认大小的线程池。
方案
适当调整系统参数,或将任务提交到自定义的 ForkJoinPool 实例。
讨论
根据 Javadoc 对 java.util.concurrent.ForkJoinPool 类的描述,可以通过以下三种系统属性控制通用线程池的构建:
java.util.concurrent.ForkJoinPool.common.parallelismjava.util.concurrent.ForkJoinPool.common.threadFactoryjava.util.concurrent.ForkJoinPool.common.exceptionHandler
之前曾经提到过,默认情况下,通用线程池大小等于 JVM 上可用的处理器数量,它由 Runtime.getRuntime().availableProcessors() 确定。parallelism 标志用于指定并行级别(parallelism level),它是一个非负整数,可以通过编程方式或命令行方式来设置。例 9-12 显示了如何采用 System.setProperty 方法创建所需的并行级别。
例 9-12 采用编程方式设置通用线程池大小
- System.setProperty(
- "java.util.concurrent.ForkJoinPool.common.parallelism", "20");
- long total = LongStream.rangeClosed(1, 3_000_000)
- .parallel()
- .sum();
- int poolSize = ForkJoinPool.commonPool().getPoolSize();
- System.out.println("Pool size: " + poolSize); ➊
➊ 打印 Pool size: 20
将通用线程池大小设置为大于可用的 CPU 核心数无助于性能提升 11。
11关于不同并发实现的性能比较,Alex Zhitnitsky 在 Fork/Join Framework vs. Parallel Streams vs. ExecutorService: The Ultimate Fork/Join Benchmark 一文中做了深入讨论。——译者注
在命令行中,可以像使用任何系统属性一样使用 -D 标志。请注意,编程设置将覆盖命令行设置,如例 9-13 所示。
例 9-13 采用系统参数设置通用线程池大小
$ java -cp build/classes/main concurrency.CommonPoolSizePool size: 20// 注释掉System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "20");$ java -cp build/classes/main concurrency.CommonPoolSizePool size: 7$ java -cp build/classes/main \-Djava.util.concurrent.ForkJoinPool.common.parallelism=10 \concurrency.CommonPoolSizePool size: 10
本例在一台 8 核计算机上运行。默认的线程池大小为 7,但如果将 main 线程包括在内,则默认情况下共有 8 个活动线程。
自定义ForkJoinPool
ForkJoinPool 类定义了一个构造函数,它传入一个整数作为并行级别。我们可以借此创建有别于通用线程池的自定义线程池,并将任务提交给它。
例 9-14 显示了如何创建自定义线程池。
例 9-14 创建自定义
ForkJoinPool
- ForkJoinPool pool = new ForkJoinPool(15); ➊
- ForkJoinTask<Long> task = pool.submit( ➋
- () -> LongStream.rangeClosed(1, 3_000_000)
- .parallel()
- .sum());
- try {
- total = task.get(); ➌
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- } finally {
- pool.shutdown();
- }
- poolSize = pool.getPoolSize();
- System.out.println("Pool size: " + poolSize); ➍
❶ 实例化一个大小为 15 的 ForkJoinPool
❷ 提交 Callable 作为任务
❸ 执行任务并等待回复
❹ 打印 Pool size: 15
大部分情况下,在流上调用 parallel 方法时所用的通用线程池都能满足要求。如果需要改变其大小,请调整系统属性;如果仍然无法满足要求,请尝试创建自定义 ForkJoinPool 并将任务提交给它。
无论如何,在确定采用何种长期解决方案之前,请务必收集相关的性能数据。
另见
通过自定义线程池实现并行计算的另一种方案是采用 CompletableFuture,相关讨论请参见范例 9.5。
将通用线程池大小设置为大于可用的 CPU 核心数无助于性能提升 11。