在 Java 8 中使用 parallelstream() 填充 Map 是否安全

2022-09-02 09:41:55

我有一个包含 100 万个对象的列表,我需要将其填充到 Map 中。现在,我想减少将其填充到Map中的时间,为此,我计划使用Java 8 parallelstream(),如下所示:

List<Person> list = new LinkedList<>();
Map<String, String> map = new HashMap<>();
list.parallelStream().forEach(person ->{
    map.put(person.getName(), person.getAge());
});

我想问一下,通过并行线程填充这样的Map是否安全。是不是有可能出现并发问题,并且某些数据可能会在Map中丢失?


答案 1

使用收集到.但是,使用起来并不安全,并且消费者将东西添加到.parallelStream()HashMapparallelStream()forEachHashMap

HashMap不是同步类,并且尝试同时在其中放置元素将无法正常工作。这就是 forEach 将要执行的操作,它将调用给定的使用者,该使用者可能同时从多个线程将元素放入 中。如果你想要一个简单的代码来演示这个问题:HashMap

List<Integer> list = IntStream.range(0, 10000).boxed().collect(Collectors.toList());
Map<Integer, Integer> map = new HashMap<>();
list.parallelStream().forEach(i -> {
    map.put(i, i);
});
System.out.println(list.size());
System.out.println(map.size());

确保运行它几次。很有可能(并发的乐趣),操作后的打印地图大小不是10000,这是列表的大小,但略小。

这里的解决方案,一如既往,不是使用 ,而是使用方法和内置的toMap的可变约简方法:forEachcollect

Map<Integer, Integer> map = list.parallelStream().collect(Collectors.toMap(i -> i, i -> i));

在上面的示例代码中使用该行代码,您可以放心,映射大小将始终为 10000。流 API 确保可以安全地收集到非线程安全容器中,即使并行也是如此。这也意味着你不需要使用toConcurrentMap来确保安全,如果你特别想要一个结果,而不是一般的;如果你特别想要一个结果,那么这个收集器是需要的;但就线程安全性而言,您可以同时使用两者。ConcurrentMapMapcollect


答案 2

HashMap不是线程安全的,但 ConcurrentHashMap 是;用它来代替

Map<String, String> map = new ConcurrentHashMap<>();

,您的代码将按预期工作。


性能比较与forEach()toMap()

在JVM预热后,使用1M元素,使用并行流并使用中值时序,该版本始终比版本快2-3倍。forEach()toMap()

结果在全唯一、25% 重复和 100% 重复输入之间是一致的。


推荐