Java 来自哈希集的并行流不';不要并行运行

Java 来自哈希集的并行流不';不要并行运行,java,lambda,parallel-processing,java-8,java-stream,Java,Lambda,Parallel Processing,Java 8,Java Stream,我有一组要并行处理的元素。当我使用列表时,并行性工作。但是,当我使用集合时,它不会并行运行 我编写了一个代码示例来说明问题: public static void main(String[] args) { ParallelTest test = new ParallelTest(); List<Integer> list = Arrays.asList(1,2); Set<Integer> set = new HashSet<>(l

我有一组要并行处理的元素。当我使用
列表时,并行性工作。但是,当我使用
集合时,它不会并行运行

我编写了一个代码示例来说明问题:

public static void main(String[] args) {
    ParallelTest test = new ParallelTest();

    List<Integer> list = Arrays.asList(1,2);
    Set<Integer> set = new HashSet<>(list);

    ForkJoinPool forkJoinPool = new ForkJoinPool(4);

    System.out.println("set print");
    try {
        forkJoinPool.submit(() ->
            set.parallelStream().forEach(test::print)
        ).get();
    } catch (Exception e) {
        return;
    }

    System.out.println("\n\nlist print");
    try {
        forkJoinPool.submit(() ->
            list.parallelStream().forEach(test::print)
        ).get();
    } catch (Exception e) {
        return;
    }   
}

private void print(int i){
    System.out.println("start: " + i);
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    }
    System.out.println("end: " + i);
}
我们可以看到
集中的第一个元素必须在处理第二个元素之前完成。对于
列表
,第二个元素在第一个元素完成之前开始


您能否告诉我是什么原因导致此问题,以及如何使用
Set
集合避免此问题

我可以重现您看到的行为,其中并行性与您指定的fork-join-pool并行性不匹配。将fork-join-pool并行度设置为10,并将集合中的元素数增加到50后,我看到基于列表的流的并行度仅上升到6,而基于集合的流的并行度从未超过2

但是,请注意,这种将任务提交到fork-join池以在该池中运行并行流的技术是一种实现“技巧”,不能保证有效。实际上,用于执行并行流的线程或线程池是未指定的。默认情况下,使用公共fork-join池,但在不同的环境中,可能会使用不同的线程池。(考虑应用服务器中的容器。)

在类中,
LEAF_TARGET
字段确定所完成的拆分量,而拆分量又决定可以实现的并行量。此字段的值基于
ForkJoinPool.getCommonPoolParallelism()
,它当然使用公共池的并行性,而不是运行任务的池

可以说,这是一个bug(请参阅OpenJDK问题),但是,这整个领域都没有明确说明。但是,系统的这一领域肯定需要开发,例如在拆分策略、可用并行量、处理阻塞任务等方面。JDK的未来版本可能会解决其中一些问题

同时,可以通过使用系统属性来控制公共fork-join池的并行性。如果将这一行添加到程序中

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "10");
您在公共池中运行流(或者如果您将它们提交到您自己的池中,该池具有足够高的并行度设置),您将看到更多的任务并行运行

还可以使用
-D
选项在命令行上设置此属性

同样,这并不是保证行为,将来可能会改变。但在可预见的未来,这种技术可能适用于JDK 8实现


更新2019-06-12:该漏洞已在JDK 10中修复,并已将修复后移植到即将发布的JDK 8u版本(8u222)。

尝试使用两个以上的元素,如10。元素什么的。使用2的结果太模糊了当您尝试使用10时,仍然无法并行所有集合元素。我需要并行运行所有元素。无论如何,这是10的输出(10个执行器池)元素集打印开始:8开始:0开始:4开始:6开始:2结束:2结束:6结束:4结束:0开始:1结束:8开始:9开始:5开始:7结束:3结束:3结束:5结束:9结束:7结束:1列表打印开始:7开始:3开始:0开始:6开始:9开始:8开始:5开始:4开始:2开始:1结束:0结束:6结束:7结束:9结束:2结束:3结束:3结束:8结束:1结束:4不是所有集合元素都并行运行,问题是这是一个bug:修复方法是使用当前
ForkJoinPool
(默认或其他)的并行性还是其他方法?不,不是bug。这就是
ForkJoinPool
的工作原理-它有一个类似于反压力机制的机制,可以防止不必要的线程扩散。我已经为您链接的问题添加了一个答案,解释了这种行为。@SotiriosDelimanolis在这里也看到了Dimitar的评论。我看到你们也在@DimitarDimitrov上讨论这个问题,我认为这比你们现在的表现要简单。“这可能是一个bug”语句是关于流中的拆分行为的。它总是基于公共池的并行性进行拆分。但是,如果流的目标是另一个池(使用未记录的hack),则拆分仍然由公共池的并行性控制,而不是目标池的并行性。@StuartMarks yes,关于
AbstractTask
行为,您完全正确,而我关于
ForkJoinPool
背压是根本原因的回答是错误的。谢谢你们对我的宽容,我会相应地更新我的答案。至于@SotiriosDelimanolis在这里的评论,不管这是否可以被归类为bug,也不管是否应该支持使用非默认池,修复这一问题可能需要更多实质性的更改,因为目前流不知道它将在其中运行的池的并行度级别。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "10");