Java Iterator通过modCount与expectedModCount比对实现Fail-Fast机制,仅检测结构性修改(如add/remove),遍历时须用iterator.remove()而非集合remove(),否则抛ConcurrentModificationException。

为什么遍历集合时修改会抛 ConcurrentModificationException
Java 的 Iterator 默认启用 Fail-Fast 机制,不是为了“阻止并发”,而是为了快速暴露**结构性修改**(比如增删元素)导致的遍历逻辑错乱。只要集合被 add()、remove() 等方法改过结构,而迭代器没感知,下一次调用 next() 或 hasNext() 就会立即报错。
常见错误现象:
- 在
for-each循环里调用list.remove(obj)—— 报ConcurrentModificationException - 多线程环境下,一个线程用
Iterator遍历,另一个线程调用map.put()—— 即使是HashMap也会崩
根本原因:集合内部维护一个 modCount 计数器,Iterator 创建时拷贝一份叫 expectedModCount;每次调用 next() 前都会比对两者,不一致就炸。
想边遍历边删元素?必须用 Iterator.remove()
这是唯一被允许的“遍历中修改”方式。它和集合自身的 remove() 不同:前者会同步更新 expectedModCount,后者不会。
立即学习“Java免费学习笔记(深入)”;
使用场景:
- 过滤掉满足条件的元素(如字符串为空、数值小于阈值)
- 不能用
stream().filter().collect()的老 JDK 环境(如 JDK 7)
实操建议:
- 绝不用
list.remove(obj)替代iterator.remove() -
iterator.remove()只能紧跟在next()之后调用一次,否则抛IllegalStateException - 示例:
Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); if (s == null || s.trim().isEmpty()) { it.remove(); // ✅ 正确 } }
CopyOnWriteArrayList 和 ConcurrentHashMap 怎么绕过 Fail-Fast
它们不是“禁用 Fail-Fast”,而是压根不检查 modCount —— 因为设计目标就是支持安全的并发读写。但代价明显:
-
CopyOnWriteArrayList每次写都复制整个数组,适合读多写极少(如监听器列表) -
ConcurrentHashMap迭代器弱一致性:可能看不到最近 put 的键值,也不会报错,但也不会卡死或返回脏数据 - 注意:
ConcurrentHashMap.keySet().iterator()是弱一致的,但entrySet().iterator()同样不抛ConcurrentModificationException
性能影响:
- 别在高频写场景用
CopyOnWriteArrayList,GC 压力大 -
ConcurrentHashMap的迭代器不保证反映实时状态,如果业务依赖“遍历时看到最新修改”,它就不适用
自定义集合怎么实现自己的 Fail-Fast 行为
如果你写了个继承 AbstractList 的集合类,modCount 字段和检查逻辑其实是父类自动处理的——前提是你的增删方法调用了 super.modCount++。
容易踩的坑:
- 重写
add()但忘了modCount++→ 迭代器永远不报错,但可能跳过元素或重复返回 - 在
Iterator子类里漏掉checkForComodification()调用 → 失去 Fail-Fast 保护 - 用
ArrayList做底层数组,却直接操作数组而不更新modCount→ 表面正常,实际行为不可预测
一句话:Fail-Fast 不是魔法,是靠 modCount 和显式校验撑起来的契约。谁破坏契约,谁负责后果。
最常被忽略的是:Fail-Fast 只检测“结构性修改”,不检测元素内容变更。比如 list.get(0).setName("x") 完全合法,哪怕这个对象正被迭代器拿着 —— 因为 modCount 没变。


















