并行流中使用三参数collect结合HashMap的行为解析
并行流场景下使用HashMap collect的行为分析
咱们先拆解一下你这段代码的意图:你想生成从1开始的整数流,转为并行流后取前1000个元素,然后把每个元素作为key、false作为value收集到一个HashMap里。但在并行流的场景下,这段代码会出现几个明显的问题,咱们一个个说:
1. Stream.iterate并行流的元素重复问题
Stream.iterate(1, n -> n + 1)是一个有状态的顺序生成流,它的每一个元素都依赖前一个元素。当你把它转为并行流时,Java的并行流框架无法高效地拆分这个流——因为每个线程无法独立生成不重叠的元素范围。实际执行时,多个线程可能会从同一个初始值开始生成元素,导致最终流中出现大量重复的整数。
举个例子:线程A拿到初始值1,生成1、2、3...;线程B可能也拿到初始值1,生成1、2、3...。虽然你加了limit(1000),但并行流的limit是在所有线程生成元素后再截取前1000个,这就导致最终收集到的元素里有很多重复的key。
2. HashMap并发操作的线程安全问题
你用HashMap::new作为supplier,这意味着每个并行线程都会创建自己的HashMap实例来存放处理的元素——这部分没问题。但问题出在组合器HashMap::putAll:
并行流收集时,框架会调用组合器把多个线程的HashMap合并成最终的结果Map。而HashMap本身是非线程安全的,putAll方法没有任何同步措施。当多个线程同时对同一个目标HashMap执行putAll操作时,会触发数据竞争:
- 可能出现Entry丢失,部分子Map的内容没合并进去;
- 极端情况下,会导致HashMap内部的链表/红黑树结构损坏,甚至出现死循环;
- 更常见的是偶发的
ConcurrentModificationException。
最终的行为表现
综合这两个问题,这段代码的执行结果是完全不可预测的:
- 最终的
resultMap大小大概率小于1000,因为重复的key会被覆盖; - 可能抛出并发修改异常;
- 甚至可能导致程序卡住(HashMap并发扩容时的经典死循环问题);
- 即使没有异常,Map中的key也可能不连续,或者缺失某些本该存在的数字。
正确的修复方案
如果想在并行流中安全地收集到Map里,有两种靠谱的选择:
- 使用线程安全的Map容器,比如
ConcurrentHashMap:Map<Integer, Boolean> resultMap = integers.limit(1000) .collect(ConcurrentHashMap::new, (map, value) -> map.put(value, false), ConcurrentHashMap::putAll); - 或者,如果你不需要并行流(毕竟生成1-1000的整数并行没什么性能收益),直接用串行流,HashMap完全能正常工作;
- 另外,生成连续整数的并行流,更推荐用
IntStream.rangeClosed(1, 1000).parallel(),它能高效拆分元素范围,不会出现重复元素的问题。
内容的提问来源于stack exchange,提问作者Adithya
相关产品推荐
相关产品推荐

