当前位置: 首页 > 知识库问答 >
问题:

这就是Hashmap.values()的原因。parallelStream()不能并行运行,而将它们包装在ArrayList中可以工作吗?

司空坚
2023-03-14

hashmap有两个键和值对,它们不被不同的线程并行处理。


import java.util.stream.Stream;
import java.util.Map;
import java.util.HashMap;

class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        Map<String, Integer> map = new HashMap<>();
        map.put("a", 1);
        map.put("b", 2);
        map.values().parallelStream()
              .peek(x -> System.out.println("processing "+x+" in "+Thread.currentThread()))
              .forEach(System.out::println);
    }
}

输出:

processing 1 in Thread[main,5,main]
1
processing 2 in Thread[main,5,main]
2

网址:https://ideone.com/Hkxkoz

ValueSpliterator应该尝试将HashMap数组拆分为大小为1的插槽,这意味着两个元素应该在不同的线程中处理。

来源:https://www.codota.com/code/java/methods/java8.util.HMSpliterators$ValueSpliterator/

将它们包装在ArrayList中后,它按预期工作。

        new ArrayList(map.values()).parallelStream()
              .peek(x -> System.out.println("processing "+x+" in "+Thread.currentThread()))
              .forEach(System.out::println);

输出:

processing 1 in Thread[ForkJoinPool.commonPool-worker-3,5,main]
1
processing 2 in Thread[main,5,main]
2

共有2个答案

吴俊晤
2023-03-14
匿名用户

感谢您的Holger的回答,我将在这里添加更多细节。

根本原因是< code>HashMap.values()的sizeEstimate不准确。默认情况下,HashMap的容量为16,包含2个元素,由一个数组支持。拆分器的大小估计为2。

每次,每次拆分都会将数组减半。在这种情况下,16长度的数组被分成两半,每一半8个,每一半的估计大小为1。由于元素是根据hashcode放置的,不幸的是,两个元素位于同一半。

然后 forkjoin 框架认为 1 小于 sizeThreshold,它将停止拆分并开始处理任务。

同时,arrayList 没有这个问题,因为估计的大小总是准确的。

赫连睿
2023-03-14

如本答案中所述,该问题与HashMap容量可能大于其大小以及实际值根据其哈希码分布在后备数组上的事实有关。

对于所有基于数组的拆分器,拆分逻辑基本上是相同的,无论是通过数组、ArrayList还是HashMap。为了在尽力而为的基础上获得平衡的分割,每个分割将是(索引)范围的一半,但在<code>HashMap<code>的情况下,范围内的实际元素数与范围大小不同。

原则上,每个基于范围的拆分器都可以拆分为单个元素,但是,客户端代码(即 Stream API 实现)到目前为止可能不会拆分。即使尝试拆分,决策也由预期的元素数和 CPU 内核数决定。

采用以下程序:

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    map.put("a", 1);
    map.put("b", 2);

    for(int depth: new int[] { 1, 2, Integer.MAX_VALUE }) {
        System.out.println("With max depth: "+depth);
        Tree<Spliterator<Map.Entry<String, Integer>>> spTree
            = split(map.entrySet().spliterator(), depth);
        Tree<String> valueTree = spTree.map(sp -> "estimated: "+sp.estimateSize()+" "
            +StreamSupport.stream(sp, false).collect(Collectors.toList()));
        System.out.println(valueTree);
    }
}

private static <T> Tree<Spliterator<T>> split(Spliterator<T> sp, int depth) {
    Spliterator<T> prefix = depth-- > 0? sp.trySplit(): null;
    return prefix == null?
        new Tree<>(sp): new Tree<>(null, split(prefix, depth), split(sp, depth));
}

public static class Tree<T> {
    final T value;
    List<Tree<T>> children;

    public Tree(T value) {
        this.value = value;
        children = Collections.emptyList();
    }
    public Tree(T value, Tree<T>... ch) {
        this.value = value;
        children = Arrays.asList(ch);
    }
    public <U> Tree<U> map(Function<? super T, ? extends U> f) {
        Tree<U> t = new Tree<>(value == null? null: f.apply(value));
        if(!children.isEmpty()) {
            t.children = new ArrayList<>(children.size());
            for(Tree<T> ch: children) t.children.add(ch.map(f));
        }
        return t;
    }
    public @Override String toString() {
        if(children.isEmpty()) return value == null? "": value.toString();
        final StringBuilder sb = new StringBuilder(100);
        toString(sb, 0, 0);
        return sb.toString();
    }
    public void toString(StringBuilder sb, int preS, int preEnd) {
        final int myHandle = sb.length() - 2;
        sb.append(value == null? "": value).append('\n');
        final int num = children.size() - 1;
        if (num >= 0) {
            if (num != 0) {
                for (int ix = 0; ix < num; ix++) {
                    int nPreS = sb.length();
                    sb.append(sb, preS, preEnd);
                    sb.append("\u2502 ");
                    int nPreE = sb.length();
                    children.get(ix).toString(sb, nPreS, nPreE);
                }
            }
            int nPreS = sb.length();
            sb.append(sb, preS, preEnd);
            final int lastItemHandle = sb.length();
            sb.append("  ");
            int nPreE = sb.length();
            children.get(num).toString(sb, nPreS, nPreE);
            sb.setCharAt(lastItemHandle, '\u2514');
        }
        if (myHandle > 0) {
            sb.setCharAt(myHandle, '\u251c');
            sb.setCharAt(myHandle + 1, '\u2500');
        }
    }
}

您将获得:

With max depth: 1

├─estimated: 1 [a=1, b=2]
└─estimated: 1 []

With max depth: 2

├─
│ ├─estimated: 0 [a=1, b=2]
│ └─estimated: 0 []
└─
  ├─estimated: 0 []
  └─estimated: 0 []

With max depth: 2147483647

├─
│ ├─
│ │ ├─
│ │ │ ├─estimated: 0 []
│ │ │ └─estimated: 0 [a=1]
│ │ └─
│ │   ├─estimated: 0 [b=2]
│ │   └─estimated: 0 []
│ └─
│   ├─
│   │ ├─estimated: 0 []
│   │ └─estimated: 0 []
│   └─
│     ├─estimated: 0 []
│     └─estimated: 0 []
└─
  ├─
  │ ├─
  │ │ ├─estimated: 0 []
  │ │ └─estimated: 0 []
  │ └─
  │   ├─estimated: 0 []
  │   └─estimated: 0 []
  └─
    ├─
    │ ├─estimated: 0 []
    │ └─estimated: 0 []
    └─
      ├─estimated: 0 []
      └─estimated: 0 []

在想法

因此,如前所述,如果我们拆分得足够深,拆分器可以拆分为单个元素,但是,两个元素的估计大小并不表明值得这样做。在每次拆分时,它都会将估计值减半,虽然您可能会说您感兴趣的元素是错误的,但实际上对于这里的大多数拆分器来说,这是正确的,因为当下降到最大级别时,大多数拆分器都表示一个空范围,拆分它们被证明是浪费资源。

正如在另一个答案中所说,决策是关于平衡拆分(或一般的准备工作)和预期的并行化工作,Stream实现无法事先知道。如果您事先知道每个元素的工作负载将非常高,为了证明更多的准备工作是合理的,则可以使用,例如新的ArrayList。

 类似资料:
  • 我正试图将eclipse中的java项目导出为可运行的jar,但由于某种原因,可运行的jar无法工作。如果双击可执行jar,它什么也不做。我尝试将所需库提取并打包到生成的JAR中。 所以我也尝试导出一些更简单的项目,那些工作很好。最大的区别是我的真实项目有文件:图像和xml文件。 在代码中引用它们,如下所示:

  • 我最近在我的机器上安装了nginx和php 7.0.16,但是由于某种原因,nginx下载了php文件,而不是执行它们。我已经花了几天时间在网上实施了所有可用的解决方案,但都是徒劳的。 我的nginx。配置文件是: conf.d文件夹中没有文件,启用的站点只有如下所示的默认文件 有人能告诉我,有什么问题吗?

  • 如何在gradle中导出可执行jar,并且这个jar可以运行,因为它包含引用库。 建筑格拉德尔 跑步后:gradle build 它创建build文件夹,我在build/libs/XXX中运行jar。震击器: java-jar构建/libs/XXX. jar 这里有一个执行说明: 我如何使用参考库运行它?

  • 问题内容: 我怀疑异常可能会使TimerTask停止运行,在这种情况下,我很可能需要第二个timetask来监视第一个仍在运行? 更新资料 感谢您的回答。我继承了此代码,因此有点无知… 我刚刚看到,如果我在工作中抛出未捕获的异常,TimerThread将永远停止运行。 的运行方法表明,如果引发异常,则计划的线程将永远不会再次运行。 stacktrace的结尾将是: 因此,临时解决方案是抓住一切…长

  • 问题内容: 简而言之,我需要能够将已编译的可执行文件粘贴到Java jar文件中,然后能够从Java运行它(可能通过)。 的 原因 ,是我想使用Java来包裹ImageMagick的可执行文件成分的图像处理弹性的Map Reduce任务。EMR只希望获取一个jar文件,因此我认为没有空间在旋转的数据节点上安装软件。 问题答案: jar中的可执行文件是一种资源,您可以通过Stream访问它,并将可执

  • 我正在一个水滴(数字海洋)中安装一个网站。我对正确安装带有PHP的NGINX有一个问题。我做了一个辅导https://www.digitalocean.com/community/tutorials/how-to-install-linux-nginx-mysql-php-lemp-stack-on-ubuntu-14-04但是当我试着跑的时候。php文件只是下载而已。。。例如<代码>http:/