流与迭代中Concurrent Modification Exception行为差异及原因咨询
要搞懂这三个示例的差异,核心要抓住HashMap的快速失败(fail-fast)机制以及不同遍历方式的校验逻辑:
HashMap内部用modCount记录集合的结构修改次数(新增、删除元素属于结构变化,修改已有元素的value不算),迭代器初始化时会保存当前的modCount到expectedModCount,后续每次调用next()时都会校验两者是否一致,不一致就抛出ConcurrentModificationException——但这是"尽最大努力"的检查,不是绝对触发。
示例1:增强for循环迭代values时新增元素未抛异常
增强for循环底层依赖HashMap的ValueIterator,它的遍历逻辑基于entrySet的迭代器。
初始Map只有1个元素,第一次迭代拿到value="1"后,执行put新增元素,此时modCount确实增加了,但迭代器的hasNext()会发现已经没有下一个元素需要遍历了,不会再调用next()方法——而modCount的校验只在next()中执行,所以这次修改不会触发异常。
如果初始Map有多个元素(比如同时put(1L,"1")和put(2L,"2")),迭代时新增元素就会抛出异常,因为迭代器还会继续调用next(),此时会检测到modCount和expectedModCount不一致。
示例2:Stream forEach新增元素抛出异常
Stream的forEach方法针对HashMap的values视图,使用的是延迟绑定且快速失败的spliterator。
当调用forEach时,spliterator会绑定当前的modCount,在遍历过程中,只要集合发生结构变化(新增元素会修改modCount),spliterator就会立即校验modCount,发现不一致后直接抛出ConcurrentModificationException。这和增强for循环的校验时机不同,Stream的校验更严格,只要结构变化就会触发。
示例3:Stream forEach更新原有元素未抛异常
这里的put操作是更新已有key的value,HashMap的put方法在key已存在时,只会替换value值,不会修改modCount——因为modCount只在集合结构变化(新增/删除元素)时才会增加,修改value不属于结构变化。
既然modCount没有变化,spliterator的校验就会通过,自然不会抛出异常。
内容的提问来源于stack exchange,提问作者Fatih Arslan

