Java集合 Fail-Fast(快速失败)& Fail-Safe(安全失败)学习笔记
Java集合 Fail-Fast(快速失败)& Fail-Safe(安全失败)学习笔记
一、基础定义
二者是Java集合迭代器,遍历期间集合结构被修改时的两种错误处理策略。
- Fail-Fast 快速失败
遍历中检测到集合被增删(结构性修改),立刻抛出ConcurrentModificationException,终止遍历,提前暴露代码问题。 - Fail-Safe 安全失败
遍历不操作原集合,基于快照/副本遍历;原集合怎么改都不影响当前迭代,不会抛并发修改异常。
二、底层原理
1. Fail-Fast 实现原理
- 集合内置变量
modCount:记录集合结构性修改次数(add/remove/clear都会+1); - 创建迭代器时,迭代器保存当前
modCount为expectedModCount; - 每次调用
next()/hasNext()先校验:modCount == expectedModCount; - 不相等 → 判定中间被修改,直接抛异常。
重要:Fail-Fast只是尽力而为,不是线程安全方案。多线程无同步时,modCount读写无原子性/可见性,无法100%捕获并发修改。
2. Fail-Safe 实现原理
- 写时复制:如
CopyOnWriteArrayList,每次add/remove会完整复制底层数组; - 迭代器遍历副本快照,和原集合完全隔离;
ConcurrentHashMap遍历节点快照,原集合修改不影响当前迭代;- 弱一致性:迭代只能读到遍历开始前的数据,遍历过程中新增/修改的数据无法感知。
三、代表集合、优缺点、适用场景
Fail-Fast
- 代表:
ArrayList、HashMap、HashSet(java.util包非并发集合) - 优点:无拷贝、内存开销小、遍历性能高;开发阶段快速暴露非法并发操作
- 缺点:多线程并发下不安全;遍历中增删直接报错
- 适用:单线程业务遍历、对数据实时性要求高、需要快速发现bug的场景
Fail-Safe
- 代表:
CopyOnWriteArrayList、ConcurrentHashMap(java.util.concurrent并发集合) - 优点:多线程遍历不抛异常、读无锁不阻塞、并发读稳定
- 缺点:写操作复制数组,内存/性能开销大;数据弱一致,读不到实时最新数据
- 适用:读多写少并发场景(黑白名单、路由配置、缓存列表)
四、生活化类比
- Fail-Fast:你正在看纸质文件,别人直接撕原件,立刻报警停止阅读;
- Fail-Safe:系统提前复印一份副本给你看,别人随意修改原件,你不受影响,只会读到旧复印件内容。
五、高频面试题(必背)
1. Fail-Fast 和 Fail-Safe 核心区别
并发修改处理策略不同:
- Fail-Fast:实时校验修改次数,发现改动直接抛异常中断;
- Fail-Safe:基于副本快照遍历,隔离原集合修改,无异常。
2. 单线程遍历HashMap删除元素也会报并发修改异常?
foreach底层是迭代器;直接调用map.remove()会让modCount自增,但迭代器内部expectedModCount不会同步更新,校验不匹配抛异常;
解决:必须使用迭代器自身的iterator.remove()。
3. Fail-Fast 是线程安全机制吗?
不是。仅单线程下用于检测编码错误,多线程无同步时modCount不可见,无法完全拦截并发修改,不能用来做并发安全控制。
4. CopyOnWriteArrayList 缺点
- 写操作复制全数组,大数据量下内存占用高、写入慢;
- 弱一致性,迭代只能读取遍历启动时的快照,无法感知后续新增数据。
5. 多线程读多写少集合选什么?
CopyOnWriteArrayList(列表)、ConcurrentHashMap(键值对),Fail-Safe并发安全集合。
六、开发避坑总结
- 单线程遍历普通集合,删除元素用迭代器remove,不要直接集合remove;
- 多线程共享集合且需要遍历,不要用ArrayList/HashMap,改用并发包集合;
- 大批量频繁写入场景,慎用CopyOnWriteArrayList,复制数组开销极高;
- Fail-Fast异常是bug提示,不是并发解决方案,不能靠它保证线程安全。

浙公网安备 33010602011771号