HashMap迭代时新增key为0为何触发ConcurrentModificationException
问题底层原因分析
核心前置知识点:HashMap的fail-fast机制
HashMap内部维护了一个modCount变量,每次发生结构型变更(新增/删除键值对、触发扩容等操作)时,modCount的值会自增1。
当调用keySet().iterator()生成迭代器时,迭代器会将当前的modCount的值赋值给自己的成员变量expectedModCount。每次调用迭代器的next()方法获取下一个元素前,都会先校验expectedModCount == modCount是否成立,如果不成立就会直接抛出java.util.ConcurrentModificationException。
注意:如果是修改已存在key对应的value,不属于结构型变更,不会修改modCount的值。
各个场景的具体原因解释
1. 原代码put(1,2)不报错的原因
key=1是HashMap初始化时已经存在的键,此时调用map.put(1,2)属于修改已存在key的value,不会触发modCount更新,迭代器每次校验时expectedModCount和modCount始终一致,因此不会抛出异常。
2. 改为put(0,2)抛出异常的原因
key=0是HashMap中不存在的新键,调用map.put(0,2)属于新增键值对的结构型变更,会导致modCount自增1。
此时初始化的HashMap有3个元素(key1、2、3),迭代顺序为1→2→3:
- 迭代到key=2时执行新增操作,
modCount已经变更 - 后续还需要调用
next()获取第三个元素key=3,触发校验时发现两个计数不一致,直接抛出异常
所有不存在的新key(包括小于0的负数key)执行put操作都会触发结构变更,因此都会出现相同问题。
3. 删除初始化的map.put(3,3)后运行正常的原因
删除第三个元素后,HashMap中只有key1、2两个元素,迭代顺序为1→2:
- 迭代到key=2时执行新增操作,
modCount虽然变更 - 本轮
next()已经执行完,后续判断it.hasNext()会返回false,循环直接结束,不会再调用next()触发校验逻辑,因此不会抛出异常。
注意这属于极端场景下的巧合,不是合法的操作方式,线上环境绝对不能依赖这个特性写代码。
正确的遍历过程中修改HashMap的方式是使用迭代器自带的
remove()方法,如果需要新增元素建议先把要新增的键值对暂存到另一个集合,遍历结束后再批量插入。
内容的提问来源于stack exchange,提问作者m.antkowicz
相关产品推荐
相关产品推荐

