为什么parallelStream不使用整个可用的并行性?

时间:2015-06-12 22:15:16

标签: java multithreading java-8 java-stream fork-join

我有一个自定义ForkJoinPool创建的并行度为25。

customForkJoinPool = new ForkJoinPool(25);

我有一个包含700个文件名的列表,我使用这样的代码从S3并行下载文件并将它们转换为Java对象:

customForkJoinPool.submit(() -> {
   return fileNames
     .parallelStream()
     .map((fileName) -> {
        Logger log = Logger.getLogger("ForkJoinTest");
        long startTime = System.currentTimeMillis();
        log.info("Starting job at Thread:" + Thread.currentThread().getName());
        MyObject obj = readObjectFromS3(fileName);
        long endTime = System.currentTimeMillis();
        log.info("completed a job with Latency:" + (endTime - startTime));
        return obj;
     })
     .collect(Collectors.toList);
   });
});

当我查看日志时,我看到只使用了5个线程。平行度为25,我预计这将使用25个线程。下载文件并将文件转换为对象的平均延迟时间约为200毫秒。我错过了什么?

可能更好的问题是并行流如何在为其创建线程之前分析原始列表的分割量?在这种情况下,看起来它决定将它拆分5次并停止。

2 个答案:

答案 0 :(得分:5)

为什么要使用ForkJoinPool执行此操作?它意味着CPU绑定任务的子任务太快,无法保证个人调度。您的工作负载是IO限制的,延迟时间为200毫秒,单个调度开销可以忽略不计。

使用Executor

import static java.util.stream.Collectors.toList;
import static java.util.concurrent.CompletableFuture.supplyAsync;

ExecutorService threads = Executors.newFixedThreadPool(25);

List<MyObject> result = fileNames.stream()
        .map(fn -> supplyAsync(() -> readObjectFromS3(fn), threads))
        .collect(toList()).stream()
        .map(CompletableFuture::join)
        .collect(toList());

答案 1 :(得分:3)

我认为答案就在于......来自ForkJoinPool javadoc。

  

&#34;池通过动态添加,挂起或恢复内部工作线程来尝试维护足够的活动(或可用)线程,即使某些任务停止等待加入其他任务。但是,面对阻塞的I / O或其他非托管同步,无法保证此类调整。&#34;

在您的情况下,下载将执行阻止I / O操作。