👨💻 导语:
你是否在面试中被问到:“你知道 fail-fast 和 fail-safe 的区别吗?”这个问题不仅高频,而且经常作为并发与集合机制的结合考点出现。回答模糊,很容易被打上“不够扎实”的标签。这篇文章将帮你彻底搞懂两者的原理、底层机制、面试答题策略和实战注意事项,助你在面试中脱颖而出。
一、面试主题概述:什么是 fail-fast 和 fail-safe?
在 Java 的集合框架中,fail-fast(快速失败) 和 fail-safe(安全失败) 是描述迭代器在集合被修改时的行为机制。
- fail-fast:在遍历过程中,如果检测到集合被结构性修改(除迭代器自身的
remove()
外),会立即抛出ConcurrentModificationException
。 - fail-safe:在遍历过程中,允许集合被修改,因为其基于原始集合的拷贝副本进行遍历,不会抛出异常。
这两种机制与线程安全、集合类型、性能代价等都息息相关,是面试官判断你是否真正掌握集合底层设计思路的重要依据。
二、高频面试题汇总
- 什么是 fail-fast 和 fail-safe?它们之间的区别是什么?
- 哪些 Java 集合是 fail-fast 的?哪些是 fail-safe 的?
- 为什么 fail-fast 会抛出 ConcurrentModificationException?其原理是什么?
- 如何在并发场景下安全地遍历集合?
- CopyOnWriteArrayList 是线程安全的吗?为什么不会抛出 ConcurrentModificationException?
三、重点题目详解
❶ 什么是 fail-fast 和 fail-safe?区别在哪里?
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
for (String item : list) {
if ("A".equals(item)) {
list.remove(item); // 这里将抛出 ConcurrentModificationException
}
}
✅ 输出异常:
Exception in thread "main" java.util.ConcurrentModificationException
✅ fail-fast 特点:
- 常见于:
ArrayList
,HashMap
,HashSet
,LinkedList
等非线程安全集合。 - 触发条件:在使用迭代器遍历时,集合被结构性修改(如 add/remove)。
- 异常类型:
ConcurrentModificationException
- 实现机制:通过 modCount + expectedModCount 校验(详见下题解析)
❷ fail-fast 是怎么实现的?为什么会抛出异常?
public class CustomIterator<E> implements Iterator<E> {
private int expectedModCount = list.modCount;
public E next() {
if (expectedModCount != list.modCount) {
throw new ConcurrentModificationException();
}
// 正常迭代逻辑...
}
}
✅ 机制解析:
-
集合维护了一个
modCount
字段,每当集合结构发生变化(添加/删除)时,该值会加一。 -
迭代器初始化时会复制一个
expectedModCount
。 -
每次调用
next()
或hasNext()
时会进行校验:- 如果不一致,说明有“并发修改”,立即 fail-fast。
✅ 拓展:
- fail-fast 不是绝对安全机制,它只是快速暴露问题,不能防止并发修改带来的逻辑错误。
❸ CopyOnWriteArrayList 是如何做到 fail-safe 的?
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>();
cowList.add("X");
cowList.add("Y");
for (String item : cowList) {
cowList.remove(item); // 不会抛出异常!
}
✅ 机制解析:
- CopyOnWriteArrayList / CopyOnWriteArraySet 是 fail-safe 的代表。
- 每次写操作都会创建一份新的数组副本,不影响当前迭代器遍历的旧数组。
- 因此,不会抛出 ConcurrentModificationException。
✅ 面试提醒:
- 虽然 CopyOnWrite 很安全,但代价是每次写操作都开销较大,因此不适合高频写场景。
❹ 如何在并发环境下安全地遍历集合?
✅ 推荐做法:
- 使用线程安全集合:如
CopyOnWriteArrayList
、ConcurrentHashMap
。 - 手动加锁:给集合加外部锁(不推荐,易出错)
- 使用
Collections.synchronizedList()
包装同步集合,但仍需注意遍历时要加锁:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
synchronized (syncList) {
for (String item : syncList) {
// 安全遍历
}
}
四、面试官视角与加分项
📌 面试官为什么爱问这类题?
- 并发敏感点检测:fail-fast 涉及集合与多线程的交集,是大厂面试高频场景。
- 底层理解测试:是否了解 modCount 的机制,是判断候选人是否“翻过源码”的分水岭。
- 项目实践与取舍能力:是否清楚在什么场景下应该用 CopyOnWrite?性能影响是什么?
🎯 如何打动面试官?
- 说出 modCount + expectedModCount 的机制;
- 能结合自己项目谈出 fail-fast 踩坑经验;
- 清楚 CopyOnWrite 的优缺点与适用场景。
五、总结与建议
✅ fail-fast & fail-safe 对比小结:
机制 | 代表集合 | 是否抛异常 | 底层机制 |
---|---|---|---|
fail-fast | ArrayList、HashMap | 是 | modCount 检测 |
fail-safe | CopyOnWriteArrayList、ConcurrentHashMap | 否 | 结构副本 |
📌 建议你牢记:
- fail-fast 是暴露问题,不是解决问题;
- 真正线程安全,建议使用 并发包下的集合类;
- 面试时不要泛泛而谈,结合源码 + 项目经验才是关键。