🧱 Java基础与集合篇 · 第9篇
主题:fail-fast 机制与 ConcurrentModificationException 深度剖析
🚀 高频指数:★★★★★
🎯 你将收获:fail-fast 原理、modCount 机制、触发条件、解决方案与面试答题模板。
💡 这是集合遍历安全性中的“灵魂一问”,面试必备。
一、面试开场问题
💬 面试官:
- 为什么在遍历 ArrayList 时删除元素会抛异常?
- fail-fast 是什么?fail-safe 又是什么?
- 如何安全地在遍历时修改集合?
答好这题,能直接体现你对集合底层一致性机制的理解。
二、什么是 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) | ❌ 不抛异常(数据弱一致) |
☕️ 口诀:“快失败即报错,安全副本不冲突。”
七、常见触发位置
| 集合类型 | 内部触发点 | 检查位置 |
|---|---|---|
| ArrayList | Itr.next() / Itr.remove() | checkForComodification() |
| HashMap | HashIterator.nextNode() | checkForComodification() |
| LinkedList | ListItr.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,一个在遍历,一个在修改 → 一定抛异常。
要并发安全,请使用:
CopyOnWriteArrayListConcurrentHashMapConcurrentLinkedQueue
十一、面试官追问清单
| 问题 | 答题要点 |
|---|---|
| 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 是警钟,不是防弹衣。”
828

被折叠的 条评论
为什么被折叠?



