Fail-fast靠modCount校验,不一致即抛ConcurrentModificationException,用于ArrayList等非线程安全集合,快速暴露遍历时误改结构的bug;fail-safe如CopyOnWriteArrayList通过快照隔离读写,不抛异常但有内存开销和弱一致性。

面试时被问到“Java集合迭代失效机制”,核心是讲清楚 fail-fast(快速失败) 和 fail-safe(安全失败) 的区别,不堆概念、不背定义,重点说清 为什么这样设计、怎么表现、用在哪儿、有什么代价。
快速失败(fail-fast)是怎么回事
它不是一种“故障”,而是一种主动检测+立即报错的设计策略。
- 底层靠
modCount(修改计数器)和expectedModCount(预期值)做版本比对:每次集合结构变化(add/remove/clear),modCount就加1;迭代器创建时记下当时的值,后续调用next()或remove()前都会校验两者是否一致 - 一旦不一致——比如遍历时另一个线程删了元素,或本线程用
list.remove()而非iterator.remove()——立刻抛ConcurrentModificationException - 只检测结构性修改(增删改容量),不检测元素内容修改(如
list.set(i, x)不影响modCount,不会触发异常) - 典型类:
ArrayList、HashMap、HashSet等java.util包下的非线程安全集合
安全失败(fail-safe)是怎么实现的
它不靠检测,而是绕开冲突源头:迭代前先复制一份数据快照,在副本上遍历。
- 原集合怎么改,都不影响当前迭代器——因为读的是快照,
modCount校验自然不存在 - 代价是内存开销(复制整个集合或分段拷贝)和数据延迟(遍历看不到迭代开始后的新修改)
- 典型类:
CopyOnWriteArrayList(写时复制)、ConcurrentHashMap(分段/跳表+弱一致性快照)等java.util.concurrent包下的并发集合 - 注意:
ConcurrentHashMap的 entrySet 迭代器是 fail-safe,但它的 keySet/valueSet 迭代器行为也遵循同样原则
面试时怎么答才显得有思考
别只罗列区别,可以补一句落地判断:
立即学习“Java免费学习笔记(深入)”;
- 如果你的场景是单线程、追求性能、需要及时发现逻辑错误(比如误在 for-each 里删元素),就用
ArrayList这类 fail-fast 集合——异常就是调试信号 - 如果要多线程遍历且允许写操作并存(比如配置监听、日志收集),选
CopyOnWriteArrayList或ConcurrentHashMap——宁可牺牲一点实时性,也要保证不崩 - 没有绝对好坏:fail-fast 是“宁可停机也不带病运行”,fail-safe 是“带病运行但不断服务”;选哪个,取决于你更怕数据不一致,还是更怕程序中断
顺便提一嘴常见误区
避免踩坑,能加分:
- “fail-fast = 线程安全”是错的——它恰恰说明不安全,只是暴露得早
- “fail-safe = 数据强一致”也是错的——它提供的是最终一致或弱一致,迭代中看不到新写入
-
Iterator.remove()是合法的,它会同步更新expectedModCount,不会触发异常 - 不要依赖
ConcurrentModificationException做业务控制逻辑,它只是 debug 辅助,JVM 不保证一定抛(极小概率 modCount 溢出回绕)


















