【问题标题】:Parallel processing of pairs of items from the list in Java8在Java8中并行处理列表中的项目对
【发布时间】:2015-08-03 21:08:07
【问题描述】:

Java8,有一个List<Item> list,我按如下顺序处理它:

ConcurrentMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
for (int i1 = 0; i1 < list.size() - 1; i1++) {
    Item item1 = list.get(i1);
    for (int i2 = i1 + 1; i2 < list.size(); i2++) {
        Item item2 = list.get(i2);
        doSomething(item1, item2);
    }
}

所以我处理列表中所有有序的项目对(item1 的索引 item2 的索引)。现在,我想为每个有序对并行运行 doSomething(item1, item2) 函数。实现这一目标的最佳策略是什么?对最快的代码感兴趣。欢迎Java8 直播。

doSomething 例如:map.put(item1.key + " " + item2.key, item1.val + item2.val);

有序对的数量是n * (n - 1) / 2,其中n 是列表的大小。我还考虑平均分配作业量以达到负载平衡(目前假设每对的执行时间相同)。因此不需要为每个有序对并行调用doSomething(item1, item2) 函数,但可能为一组准备好的对。

【问题讨论】:

  • 我回滚了编辑#4,它显着改变了问题,意味着使现有答案不合适。如果您有不同的问题,请提出而不是编辑旧问题。

标签: java list parallel-processing java-8 java-stream


【解决方案1】:
IntStream.range(0, list.size()).parallel()
   .forEach(i1 -> 
       IntStream.range(i1 + 1, list.size()).parallel()
           .forEach(i2 -> doSomething(list.get(i1), list.get(i2))));

这不必像其他任何答案一样复杂。

【讨论】:

  • @BrandonMcHomery,并行流不会为每对创建单独的线程。它更智能。
  • 不,您不应该这样做,并行流会将工作智能地划分为大小正确的线程池。对于流,您无需担心这一点。
  • @LouisWasserman 你能再解释一下你的最后评论吗?根据this我的理解是.parallel()使用ForkJoinPool
  • @alfasin,当然,那又怎样?它使用 Java 智能调整大小的公共池。
  • 我不是在批评...我以为您的意思是它创建了池,并想了解 ForkJoinPool 是否有这样的功能(创建更多实例)
【解决方案2】:

我已将特殊功能添加到我的 StreamEx 库(从版本 0.3.6 开始)只是为了解决这个问题:EntryStream.ofPairs 方法:

EntryStream.ofPairs(list).parallel()
    .forKeyValue((item1, item2) -> doSomething(item1, item2));

如果你不介意使用第三方库,你可以试试这个。在内部,它与公认的解决方案不同。它尽可能均匀地拆分n*(n-1)/2 可能对的集合,因此在这种情况下并行性可能更有效。

【讨论】:

    【解决方案3】:

    如果您不需要控制运行线程的数量(即列表相对较小),您可以像这样并行运行它们:

    for (int i1 = 0; i1 < list.size() - 1; i1++) {
        Item item1 = list.get(i1);
        for (int i2 = i1 + 1; i2 < list.size(); i2++) {
            Item item2 = list.get(i2);
            new Thread(){
                @Override
                public void run() {
                    doSomething(item1, item2);
                }
            }.start();
        }
    }
    

    如果列表很长,上面的代码将为每对项目旋转一个线程,这可能会严重影响性能。在这种情况下,我会使用ExecutorService 并创建newFixedThreadPool 来限制可以同时跨越的线程数:

    ExecutorService executor = Executors.newFixedThreadPool(5);
    
    for (int i1 = 0; i1 < list.size() - 1; i1++) {
        Item item1 = list.get(i1);
        for (int i2 = i1 + 1; i2 < list.size(); i2++) {
            Item item2 = list.get(i2);
    
            executor.execute(new Runnable(){
                @Override
                public void run() {
                    doSomething(item1, item2);
                }
            });
    
        }
    }
    

    【讨论】:

    • 为每对项目创建一个新线程可能会产生糟糕的性能。
    • @LouisWasserman 取决于列表的大小。如果只是几个项目应该不会太糟糕。也就是说,如果列表很长,最好使用固定大小的线程池。我会把它添加到答案中 - 谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 2014-05-10
    • 1970-01-01
    • 1970-01-01
    • 2018-06-17
    相关资源
    最近更新 更多