9.fail-fast 机制与 ConcurrentModificationException 深度剖析

🧱 Java基础与集合篇 · 第9篇

主题:fail-fast 机制与 ConcurrentModificationException 深度剖析

🚀 高频指数:★★★★★
🎯 你将收获:fail-fast 原理、modCount 机制、触发条件、解决方案与面试答题模板。
💡 这是集合遍历安全性中的“灵魂一问”,面试必备。


一、面试开场问题

💬 面试官:

  1. 为什么在遍历 ArrayList 时删除元素会抛异常?
  2. fail-fast 是什么?fail-safe 又是什么?
  3. 如何安全地在遍历时修改集合?

答好这题,能直接体现你对集合底层一致性机制的理解。


二、什么是 fail-fast 机制?

fail-fast(快速失败)是 Java 集合框架的安全机制
当集合在遍历过程中被结构性修改(增删元素)时,会立即抛出异常,防止数据不一致。

☕️ 一句话记忆:
“遍历中乱改,立刻报错。”


三、触发场景示例

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
    if ("B".equals(s)) {
        list.remove(s); // 抛出异常!
    }
}

输出:

Exception in thread "main" java.util.ConcurrentModificationException

四、底层原理:modCount 机制

modCount 是集合(如 ArrayList、HashMap)中的一个字段,用于记录结构修改次数。

ArrayList 中定义如下:

protected transient int modCount = 0;

每当集合结构性修改(add、remove、clear)时,都会执行:

modCount++;

而迭代器在创建时,会记录当前的期望修改次数:

private class Itr implements Iterator<E> {
    int expectedModCount = modCount;
}

在每次 next() 时进行校验:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

✅ 当遍历时检测到集合被修改(modCount变化),就会触发 fail-fast。


五、fail-fast 是怎么“快”的?

  • 检测机制不是延迟检查,而是即时发现
  • 抛出异常后立刻中止操作,防止数据错乱;
  • 不是线程安全手段,而是一种错误提醒机制

六、fail-fast 与 fail-safe 的区别

类型工作原理实现方式是否抛异常
fail-fast直接访问原集合,检测 modCount 变化普通集合(ArrayList、HashMap)✅ 抛出 ConcurrentModificationException
fail-safe迭代时基于副本(CopyOnWrite 或快照)并发集合(ConcurrentHashMap、CopyOnWriteArrayList)❌ 不抛异常(数据弱一致)

☕️ 口诀:“快失败即报错,安全副本不冲突。”


七、常见触发位置

集合类型内部触发点检查位置
ArrayListItr.next() / Itr.remove()checkForComodification()
HashMapHashIterator.nextNode()checkForComodification()
LinkedListListItr.next()checkForComodification()

八、常见错误示例

List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4));
for (Integer i : list) {
    if (i == 3) list.remove(i); // ❌ ConcurrentModificationException
}

即使你使用增强 for 循环(底层就是 Iterator),也会报错。


九、安全的三种解决方案

方案实现方式说明
✅ 使用 Iterator.remove()在迭代器中安全删除Iterator 内部同步更新 expectedModCount
✅ 使用 CopyOnWriteArrayList并发安全,基于副本修改不会影响当前遍历
✅ 使用 Stream 过滤新建集合,不直接修改原集合函数式写法简洁

示例1:Iterator.remove()

Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    if (it.next() == 3) it.remove(); // ✅ 安全
}

示例2:CopyOnWriteArrayList

List<Integer> list = new CopyOnWriteArrayList<>(Arrays.asList(1,2,3,4));
for (Integer i : list) {
    if (i == 3) list.remove(i); // ✅ 不报错
}

示例3:Stream 方式

list = list.stream()
           .filter(i -> i != 3)
           .collect(Collectors.toList());

十、fail-fast 在多线程中的陷阱

fail-fast 不是 并发控制机制:
如果两个线程同时操作同一个 ArrayList,一个在遍历,一个在修改 → 一定抛异常。

要并发安全,请使用:

  • CopyOnWriteArrayList
  • ConcurrentHashMap
  • ConcurrentLinkedQueue

十一、面试官追问清单

问题答题要点
fail-fast 是什么?遍历期间检测结构性修改并报错机制。
为什么抛异常?modCount 与 expectedModCount 不一致。
怎么避免?Iterator.remove、CopyOnWriteArrayList、Stream。
fail-safe 是什么?基于副本的安全遍历,不报错。
fail-fast 是线程安全的吗?否,只是检测机制。

十二、项目实践建议

场景推荐方式
单线程修改遍历使用 Iterator.remove()
多线程读多写少使用 CopyOnWriteArrayList
多线程频繁写使用 ConcurrentHashMap
高性能流式操作使用 Stream.filter()

实际工程中,如果你要修改集合,请不要用增强 for,要用 Iterator 或 Stream


十三、口诀记忆

☕️ “遍历乱改立刻炸,安全修改用迭代;副本集合不冲突,fail-fast早预警。”


十四、小结

知识点要点
fail-fast 原理modCount 检测结构修改
报错类型ConcurrentModificationException
fail-safe 对比基于副本,弱一致性,不报错
安全修改方式Iterator.remove / CopyOnWrite / Stream
是否线程安全否,仅检测机制

✅ 一句话总结:“fail-fast 是警钟,不是防弹衣。”


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值