Java集合迭代失效机制核心在于modCount校验:迭代器创建时快照modCount,每次next()检查是否被修改,单线程直接调用list.remove()也会触发ConcurrentModificationException;fail-fast是主动报错防误操作,fail-safe(如CopyOnWriteArrayList)通过快照实现无异常遍历但不保证实时性。

Java 集合迭代失效机制的核心,不是“能不能并发”,而是“谁在改、怎么改、何时感知”。很多面试者误以为只要用了线程安全集合就万事大吉,或把 ConcurrentModificationException 简单归因为“多线程导致”,结果在单线程遍历中删元素也栽跟头。关键要分清 fail-fast 和 fail-safe 的底层逻辑,以及它们各自适用的边界。
别把“线程安全”等同于“迭代安全”
像 CopyOnWriteArrayList 或 ConcurrentHashMap 确实是线程安全的,但它们的迭代器不反映实时修改——这是设计使然,不是缺陷。面试时若说“用了 CopyOnWriteArrayList 就能边遍历边看到新增元素”,就掉进误区了。
- 它的迭代器基于创建时刻的数组快照,后续所有写操作都生成新副本,旧迭代器仍读旧数据
- 这意味着:新增元素不可见、已删元素仍可能被遍历到、遍历过程绝不会抛
ConcurrentModificationException - 所以它适合“读远多于写 + 不要求强一致性”的场景(如监听器列表),不适合需要实时同步视图的业务逻辑
单线程里也会触发 fail-fast,根源在 modCount 检查
很多人以为 ConcurrentModificationException 只出现在多线程环境。其实只要在增强 for 循环或手动调用 iterator() 后,直接用集合自身方法(如 list.remove())修改结构,就会触发。
- 因为
ArrayList等集合内部维护一个modCount计数器,记录结构性修改次数 - 迭代器初始化时会记录当时的
modCount值,每次调用next()都校验是否一致 - 用
iterator.remove()是安全的——它会同步更新迭代器持有的expectedModCount
规避方案不能只靠换集合,得匹配操作语义
面试常问“如何安全地边遍历边删除?”,答“用 CopyOnWriteArrayList”是片面的;答“用 Iterator.remove()”才扣住本质。不同场景对应不同解法:
立即学习“Java免费学习笔记(深入)”;
- 单线程遍历删除 → 用
Iterator.remove()或removeIf()(JDK 8+) - 需要批量条件删除 → 优先用
removeIf(Predicate),语义清晰且内部已做同步处理 - 多线程读多写少 + 迭代频繁 →
CopyOnWriteArrayList合理,但需接受快照延迟 - 多线程读写均衡 + 要求弱一致性迭代 →
ConcurrentHashMap更合适,它的迭代器也不阻塞写,但比 COW 内存开销小 - 绝对禁止在遍历中调用
list.add()、map.put()等直接修改结构的方法
高频误区一句话点破
“Fail-fast 是机制,不是 bug;Fail-safe 是策略,不是万能”——前者帮你尽早发现逻辑错误(比如本不该在循环里删元素),后者帮你绕过冲突但付出复制或弱一致代价。选哪个,取决于你真正要解决的问题:是防止误操作,还是支撑高并发读?


















