知识模块
☕ Java 知识模块
二、Java 集合框架
Iterator Fail-Fast

Iterator Fail-Fast

Fail-Fast(快速失败)是 Java 集合框架中的一种错误检测机制。当多个线程(或单线程)在遍历集合的同时修改集合结构时,迭代器会快速抛出 ConcurrentModificationException,而不是在将来某个不确定的时间出现不确定的行为。


一、什么是 Fail-Fast

1.1 定义

Fail-Fast:在迭代过程中,如果检测到集合结构被修改(非迭代器自身的修改),立即抛出 ConcurrentModificationException

1.2 结构修改

操作是否结构修改
add(e)✅ 是
remove(e)✅ 是
clear()✅ 是
set(index, e)❌ 否(仅修改值)
iterator.remove()❌ 否(迭代器自身操作)
iterator.add(e)❌ 否(ListIterator 自身操作)

二、Fail-Fast 原理

2.1 modCount 机制

集合类内部维护一个 modCount 变量,记录集合的修改次数。

public class ArrayList<E> {
    // 修改次数
    protected transient int modCount = 0;
    
    public boolean add(E e) {
        modCount++;  // 每次修改 +1
        // ...
    }
    
    public E remove(int index) {
        modCount++;  // 每次修改 +1
        // ...
    }
}

2.2 迭代器检测

迭代器在创建时记录 expectedModCount,每次迭代时检查是否一致。

private class Itr implements Iterator<E> {
    // 创建迭代器时的 modCount
    int expectedModCount = modCount;
    
    public E next() {
        // 检查是否有并发修改
        checkForComodification();
        // ...
    }
    
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

2.3 流程图

┌─────────────────────────────────────────────────────────┐
│                Fail-Fast 检测流程                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 创建迭代器                                          │
│     expectedModCount = modCount (例如:5)               │
│                                                         │
│  2. 遍历过程中                                          │
│     每次调用 next()                                     │
│           │                                             │
│           ▼                                             │
│     checkForComodification()                           │
│           │                                             │
│           ▼                                             │
│     modCount == expectedModCount ?                      │
│       ┌───┴───┐                                         │
│       是      否                                         │
│       │       │                                         │
│       ▼       ▼                                         │
│    继续遍历  抛出 ConcurrentModificationException        │
│                                                         │
└─────────────────────────────────────────────────────────┘

三、示例代码

3.1 触发 Fail-Fast

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
 
// 单线程遍历时修改集合
for (String s : list) {
    System.out.println(s);
    if (s.equals("B")) {
        list.remove(s);  // 抛出 ConcurrentModificationException
    }
}
 
// 原因:foreach 循环使用迭代器,list.remove() 修改了 modCount
// 但迭代器的 expectedModCount 没有更新,导致不一致

3.2 使用迭代器删除

List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
 
// 使用迭代器删除
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String s = iterator.next();
    if (s.equals("B")) {
        iterator.remove();  // ✅ 正确,迭代器会同步 expectedModCount
    }
}
 
System.out.println(list);  // [A, C]

3.3 迭代器 remove 源码

public void remove() {
    if (lastRet < 0)
        throw new IllegalStateException();
    checkForComodification();
    
    try {
        ArrayList.this.remove(lastRet);
        cursor = lastRet;
        lastRet = -1;
        expectedModCount = modCount;  // 同步 modCount
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

四、Fail-Fast vs Fail-Safe

4.1 对比

对比项Fail-FastFail-Safe
机制直接操作原集合复制原集合
修改检测检测到修改抛异常不检测,正常遍历
内存开销高(需要复制)
实时性高(反映最新状态)低(遍历副本)
典型实现ArrayList, HashMapCopyOnWriteArrayList, ConcurrentHashMap

4.2 Fail-Safe 示例

// CopyOnWriteArrayList - Fail-Safe
List<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
 
for (String s : list) {
    System.out.println(s);
    if (s.equals("B")) {
        list.remove(s);  // ✅ 不会抛异常
    }
}
// 输出:A, B, C(遍历的是副本,remove 影响原集合但不影响遍历)
System.out.println(list);  // [A, C]
 
// ConcurrentHashMap 的迭代器
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("A", 1);
map.put("B", 2);
 
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey());
    map.remove("B");  // ✅ 不会抛异常
}

4.3 CopyOnWriteArrayList 原理

public class CopyOnWriteArrayList<E> {
    private transient volatile Object[] array;
    
    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);
            retu