一、核心概念对比
| 特性 | Fail-Fast(快速失败) | Fail-Safe(安全失败) |
|---|---|---|
| 设计理念 | 立即暴露错误,防止数据不一致 | 容错优先,保证遍历可继续 |
| 并发修改 | 抛出 ConcurrentModificationException |
允许修改,不影响遍历 |
| 迭代器类型 | 工作在当前集合上 | 工作在集合副本或快照上 |
| 内存开销 | 低(只维护修改计数) | 高(需要复制数据) |
| 性能影响 | 遍历时检查修改,轻微开销 | 复制数据,较大开销 |
| 实时性 | 实时反映集合变化 | 遍历期间看不到新修改 |
| 典型实现 | ArrayList, HashMap, HashSet |
CopyOnWriteArrayList, ConcurrentHashMap |
二、Fail-Fast(快速失败)机制深度解析
1. 实现原理:modCount机制
java
// AbstractList.java 中的关键字段
protected transient int modCount = 0; // 修改计数器
// ArrayList的add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1);
elementData[size++] = e;
modCount++; // ⭐ 关键:每次修改递增
return true;
}
// ArrayList.Itr迭代器实现
private class Itr implements Iterator<E> {
int expectedModCount = modCount; // 创建迭代器时记录当前modCount
public E next() {
checkForComodification(); // ⭐ 关键:检查是否被修改
// ... 返回元素
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException(); // ⭐ 快速失败!
}
}
2. 触发场景示例
java
import java.util.*;
public class FailFastDemo {
public static void main(String[] args) {
// 场景1:单线程迭代中修改
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
// 错误示例:迭代中直接修改集合
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
System.out.println(item);
if ("B".equals(item)) {
list.remove(item); // ❌ 抛出 ConcurrentModificationException
// list.add("D"); // ❌ 同样会抛出异常
}
}
// 场景2:多线程并发修改
List<Integer> numbers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
numbers.add(i);
}
// 线程1:遍历
Thread t1 = new Thread(() -> {
try {
for (Integer num : numbers) { // 隐式使用迭代器
System.out.println("T1: " + num);
Thread.sleep(1);
}
} catch (Exception e) {
System.out.println("T1异常: " + e); // 大概率会抛出异常
}
});
// 线程2:修改
Thread t2 = new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
numbers.add(i * 100);
Thread.sleep(2);
}
} catch (Exception e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
}
3. 合法的修改方式
java
// 方法1:使用迭代器的remove方法(仅限删除)
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String item = it.next();
if ("B".equals(item)) {
it.remove(); // ✅ 通过迭代器自身删除,不会抛出异常
}
}
// 方法2:使用for-i循环(非迭代器)
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (int i = 0; i < list.size(); i++) {
if ("B".equals(list.get(i))) {
list.remove(i); // ✅ 可以,但需要调整索引
i--; // 删除后索引回退
}
}
// 方法3:使用removeIf(Java 8+)
list.removeIf(item -> "B".equals(item)); // ✅ 安全删除
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
三、Fail-Safe(安全失败)机制深度解析
1. CopyOnWriteArrayList 实现原理
java
// CopyOnWriteArrayList 的核心机制
public class CopyOnWriteArrayList<E> {
private transient volatile Object[] array; // volatile 保证可见性
// 迭代器工作在数组快照上
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
// 写操作:复制新数组
public boolean add(E e) {
synchronized (lock) {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // ⭐ 复制数组
newElements[len] = e;
setArray(newElements); // ⭐ 原子性替换引用
return true;
}
}
// 迭代器实现:持有创建时的数组快照
static final class COWIterator<E> implements ListIterator<E> {
private final Object[] snapshot; // ⭐ 快照,不会改变
private int cursor;
COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements; // 创建时复制引用(注意:不是深拷贝)
}
public E next() {
if (!hasNext()) throw new NoSuchElementException();
return (E) snapshot[cursor++]; // ⭐ 始终从快照读取
}
}
}
2. ConcurrentHashMap 实现原理
java
// ConcurrentHashMap 的弱一致性迭代器
public class ConcurrentHashMap<K,V> {
// 分段锁或CAS实现
// 迭代器实现特点:
// 1. 不抛出 ConcurrentModificationException
// 2. 反映创建迭代器时或之后的某些更新
// 3. 但不保证反映所有最新修改
public Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
// EntryIterator 可能在遍历时:
// - 看到新增的条目
// - 看不到已删除的条目
// - 可能看到部分修改的条目
}
3. Fail-Safe 示例
java
import java.util.concurrent.*;
import java.util.*;
public class FailSafeDemo {
public static void main(String[] args) {
// 示例1:CopyOnWriteArrayList
List<String> cowList = new CopyOnWriteArrayList<>();
cowList.addAll(Arrays.asList("A", "B", "C"));
System.out.println("=== CopyOnWriteArrayList 示例 ===");
// 迭代期间修改集合
for (String item : cowList) {
System.out.println("遍历: " + item);
if ("B".equals(item)) {
cowList.add("NEW"); // ✅ 不会抛出异常
cowList.remove("A"); // ✅ 不会影响当前遍历
}
}
System.out.println("最终集合: " + cowList); // 包含NEW,不含A
// 示例2:ConcurrentHashMap
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
concurrentMap.put("A", 1);
concurrentMap.put("B", 2);
concurrentMap.put("C", 3);
System.out.println("\n=== ConcurrentHashMap 示例 ===");
// 迭代期间修改
for (Map.Entry<String, Integer> entry : concurrentMap.entrySet()) {
System.out.println("遍历: " + entry.getKey() + "=" + entry.getValue());
if ("B".equals(entry.getKey())) {
concurrentMap.put("D", 4); // ✅ 可能看到也可能看不到
concurrentMap.remove("A"); // ✅ 可能看不到删除
}
}
// 示例3:多线程安全遍历
List<Integer> safeList = new CopyOnWriteArrayList<>();
for (int i = 0; i < 5; i++) safeList.add(i);
Thread writer = new Thread(() -> {
for (int i = 0; i < 3; i++) {
safeList.add(i * 10);
try { Thread.sleep(50); } catch (InterruptedException e) {}
}
});
Thread reader = new Thread(() -> {
// 安全遍历,不会抛出异常
for (Integer num : safeList) {
System.out.println("读取: " + num);
try { Thread.sleep(100); } catch (InterruptedException e) {}
}
});
writer.start();
reader.start();
}
}
四、两种机制的对比分析
1. 内存与性能影响
java
// 性能测试对比
public class PerformanceTest {
public static void main(String[] args) {
int size = 100000;
// ArrayList (Fail-Fast)
List<Integer> arrayList = new ArrayList<>();
for (int i = 0; i < size; i++) arrayList.add(i);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
arrayList.add(i); // 直接修改
}
System.out.println("ArrayList 写耗时: " + (System.currentTimeMillis() - start) + "ms");
// CopyOnWriteArrayList (Fail-Safe)
List<Integer> cowList = new CopyOnWriteArrayList<>(arrayList);
start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
cowList.add(i); // 每次写都要复制数组
}
System.out.println("CopyOnWriteArrayList 写耗时: " + (System.currentTimeMillis() - start) + "ms");
// 读操作对比
start = System.currentTimeMillis();
for (Integer num : arrayList) { /* 遍历 */ }
System.out.println("ArrayList 遍历耗时: " + (System.currentTimeMillis() - start) + "ms");
start = System.currentTimeMillis();
for (Integer num : cowList) { /* 遍历 */ }
System.out.println("CopyOnWriteArrayList 遍历耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}
典型结果:
-
写操作 :ArrayList 比 CopyOnWriteArrayList 快10-100倍
-
读操作:两者相差不大,CopyOnWriteArrayList 稍快(无锁)
-
内存占用 :CopyOnWriteArrayList 在写时可能占用双倍内存
2. 数据一致性对比
java
// 数据一致性演示
List<String> failFastList = Collections.synchronizedList(new ArrayList<>());
List<String> failSafeList = new CopyOnWriteArrayList<>();
// 初始化
for (int i = 0; i < 3; i++) {
failFastList.add("Item" + i);
failSafeList.add("Item" + i);
}
// 多线程同时遍历和修改
ExecutorService executor = Executors.newFixedThreadPool(4);
// 对Fail-Fast集合操作(可能失败)
for (int i = 0; i < 2; i++) {
executor.submit(() -> {
try {
synchronized (failFastList) { // 必须加锁!
for (String item : failFastList) {
System.out.println("FailFast读: " + item);
Thread.sleep(10);
}
}
} catch (Exception e) {
System.out.println("FailFast异常: " + e.getClass().getName());
}
});
executor.submit(() -> {
try {
synchronized (failFastList) { // 必须加锁!
failFastList.add("NewItem");
System.out.println("FailFast写: 添加元素");
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
// 对Fail-Safe集合操作(安全)
for (int i = 0; i < 2; i++) {
executor.submit(() -> {
for (String item : failSafeList) { // 无需加锁!
System.out.println("FailSafe读: " + item);
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
});
executor.submit(() -> {
failSafeList.add("NewItem"); // 无需加锁!
System.out.println("FailSafe写: 添加元素");
});
}
executor.shutdown();
五、生产环境选型指南
选择 Fail-Fast(ArrayList/HashMap)的场景
java
// 场景1:单线程环境
public class SingleThreadProcessor {
private List<Order> orders = new ArrayList<>(); // ✅ 合适
public void processOrders() {
// 单线程操作,无需担心并发修改
orders.removeIf(order -> order.isExpired());
// ... 其他操作
}
}
// 场景2:读多写少,且写时可控
public class CachedData {
private Map<String, Data> cache = new HashMap<>(); // ✅ 合适
// 定时全量更新缓存
public void refreshCache() {
Map<String, Data> newData = loadFromDB();
synchronized (cache) {
cache.clear();
cache.putAll(newData); // 批量更新,避免迭代中修改
}
}
// 读操作(占99%的调用)
public Data getData(String key) {
return cache.get(key); // 快速读取
}
}
选择 Fail-Safe(CopyOnWriteArrayList/ConcurrentHashMap)的场景
java
// 场景1:监听器列表(读远多于写)
public class EventManager {
// 监听器很少添加/删除,但频繁遍历通知
private final List<EventListener> listeners =
new CopyOnWriteArrayList<>(); // ✅ 完美匹配
public void addListener(EventListener listener) {
listeners.add(listener); // 写操作少
}
public void fireEvent(Event event) {
// 频繁遍历,且不能因为添加监听器而失败
for (EventListener listener : listeners) {
listener.onEvent(event); // 安全遍历
}
}
}
// 场景2:高并发配置管理
public class Configuration {
// 配置可能被多个线程读取,偶尔更新
private final ConcurrentMap<String, String> config =
new ConcurrentHashMap<>(); // ✅ 合适
// 热更新配置
public void updateConfig(String key, String value) {
config.put(key, value); // 线程安全更新
}
// 多个线程同时读取配置
public String getConfig(String key) {
return config.get(key); // 无锁读取,高性能
}
}
// 场景3:实时数据快照
public class MetricsCollector {
// 需要生成一致性的快照用于报告
private final CopyOnWriteArrayList<Metric> metrics =
new CopyOnWriteArrayList<>();
public void addMetric(Metric metric) {
metrics.add(metric);
}
public Report generateReport() {
// 生成报告时需要一致的数据视图
List<Metric> snapshot = new ArrayList<>(metrics); // 已经是快照
// 安全处理,即使此时有新的metrics添加
return processSnapshot(snapshot);
}
}
混合使用策略
java
public class SmartCollectionManager {
// 根据使用模式动态选择
private List<String> data;
public SmartCollectionManager(boolean concurrentNeeded) {
if (concurrentNeeded) {
this.data = new CopyOnWriteArrayList<>();
} else {
this.data = new ArrayList<>();
}
}
// 包装器模式:对外提供安全接口
public static <T> List<T> asSafeList(List<T> original) {
if (original instanceof CopyOnWriteArrayList ||
original instanceof ConcurrentHashMap) {
return original;
}
// 返回不可修改的视图,防止并发修改异常
return Collections.unmodifiableList(original);
}
}
六、面试深度回答要点
基础回答(校招/初级)
"Fail-Fast是快速失败机制,在迭代过程中如果集合被修改,会立即抛出ConcurrentModificationException,比如ArrayList。Fail-Safe是安全失败机制,允许在迭代时修改集合,比如CopyOnWriteArrayList,它在修改时会创建副本,不影响正在进行的遍历。"
进阶回答(社招/中级)
"Fail-Fast通过modCount机制实现,迭代器保存创建时的modCount,每次操作前检查是否一致。Fail-Safe通过数据副本或快照实现,如CopyOnWriteArrayList在写时复制整个数组。Fail-Fast适合单线程或完全同步的场景,Fail-Safe适合读多写少的高并发场景。需要注意的是,Fail-Safe的'安全'是有代价的:内存开销和弱一致性。"
深度回答(高级/架构师)
"这本质上是并发设计哲学的差异 。Fail-Fast采用'快速暴露问题'的设计理念,适合开发调试阶段,防止隐藏的并发bug。Fail-Safe采用'最大化可用性'理念,适合生产环境高可用要求。从实现看,Fail-Fast是乐观检查 (检查失败就抛异常),Fail-Safe是悲观复制(假设会修改所以提前复制)。在实际架构中,我会根据读写比例选择:读多写少用CopyOnWrite,写多用ConcurrentHashMap,单线程或完全同步控制用普通集合。在微服务配置中心、监听器模式等场景中,CopyOnWrite是标准解决方案。"
篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc
需要全套面试笔记及答案
【点击此处即可/免费获取】
高频扩展问题
-
如何安全地删除ArrayList中的元素?
-
使用迭代器的remove()方法
-
使用removeIf()(Java 8+)
-
从后往前遍历删除
-
-
CopyOnWriteArrayList的迭代器支持remove操作吗?
- 不支持,会抛出UnsupportedOperationException
-
ConcurrentHashMap是Fail-Safe吗?
- 是,但它是弱一致性的,可能看到部分更新
-
Collections.synchronizedList是Fail-Safe吗?
- 不是,它只是同步包装器,迭代时仍需要外部同步
-
Fail-Safe集合一定线程安全吗?
- 是的,但要注意复合操作仍需同步
java
// 即使是ConcurrentHashMap,这个操作也不是原子的 if (!map.containsKey(key)) { map.put(key, value); // ❌ 仍然需要putIfAbsent }
掌握这两种机制的差异,不仅能通过面试,更能帮助你在实际开发中根据场景选择合适的集合类型,写出高性能且健壮的并发代码。