Java集合 Fail-Fast(快速失败)& Fail-Safe(安全失败)学习笔记

Java集合 Fail-Fast(快速失败)& Fail-Safe(安全失败)学习笔记

一、基础定义

二者是Java集合迭代器,遍历期间集合结构被修改时的两种错误处理策略。

  1. Fail-Fast 快速失败
    遍历中检测到集合被增删(结构性修改),立刻抛出 ConcurrentModificationException,终止遍历,提前暴露代码问题。
  2. Fail-Safe 安全失败
    遍历不操作原集合,基于快照/副本遍历;原集合怎么改都不影响当前迭代,不会抛并发修改异常。

二、底层原理

1. Fail-Fast 实现原理

  1. 集合内置变量 modCount:记录集合结构性修改次数(add/remove/clear都会+1);
  2. 创建迭代器时,迭代器保存当前 modCountexpectedModCount
  3. 每次调用 next()/hasNext() 先校验:modCount == expectedModCount
  4. 不相等 → 判定中间被修改,直接抛异常。

重要:Fail-Fast只是尽力而为,不是线程安全方案。多线程无同步时,modCount读写无原子性/可见性,无法100%捕获并发修改。

2. Fail-Safe 实现原理

  1. 写时复制:如CopyOnWriteArrayList,每次add/remove会完整复制底层数组;
  2. 迭代器遍历副本快照,和原集合完全隔离;
  3. ConcurrentHashMap 遍历节点快照,原集合修改不影响当前迭代;
  4. 弱一致性:迭代只能读到遍历开始前的数据,遍历过程中新增/修改的数据无法感知。

三、代表集合、优缺点、适用场景

Fail-Fast

  • 代表:ArrayList、HashMap、HashSet(java.util包非并发集合)
  • 优点:无拷贝、内存开销小、遍历性能高;开发阶段快速暴露非法并发操作
  • 缺点:多线程并发下不安全;遍历中增删直接报错
  • 适用:单线程业务遍历、对数据实时性要求高、需要快速发现bug的场景

Fail-Safe

  • 代表:CopyOnWriteArrayList、ConcurrentHashMap(java.util.concurrent并发集合)
  • 优点:多线程遍历不抛异常、读无锁不阻塞、并发读稳定
  • 缺点:写操作复制数组,内存/性能开销大;数据弱一致,读不到实时最新数据
  • 适用:读多写少并发场景(黑白名单、路由配置、缓存列表)

四、生活化类比

  1. Fail-Fast:你正在看纸质文件,别人直接撕原件,立刻报警停止阅读;
  2. 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 缺点

  1. 写操作复制全数组,大数据量下内存占用高、写入慢;
  2. 弱一致性,迭代只能读取遍历启动时的快照,无法感知后续新增数据。

5. 多线程读多写少集合选什么?

CopyOnWriteArrayList(列表)、ConcurrentHashMap(键值对),Fail-Safe并发安全集合。

六、开发避坑总结

  1. 单线程遍历普通集合,删除元素用迭代器remove,不要直接集合remove;
  2. 多线程共享集合且需要遍历,不要用ArrayList/HashMap,改用并发包集合;
  3. 大批量频繁写入场景,慎用CopyOnWriteArrayList,复制数组开销极高;
  4. Fail-Fast异常是bug提示,不是并发解决方案,不能靠它保证线程安全。
posted @ 2026-06-23 22:11  堭鍙銤  阅读(12)  评论(0)    收藏  举报