【Java面试经典】集合的快速失败原理

130 阅读2分钟
  1. 快速失败机制概述

    • 快速失败(fail - fast)是 Java 集合框架中的一种错误检测机制。它用于在遍历集合的过程中,如果集合的结构被修改(除了通过迭代器自身的修改方法),就抛出ConcurrentModificationException异常,以防止出现不可预测的行为。
  2. ArrayList为例结合源码分析

    • ArrayList的基本结构与modCount变量

      • ArrayList的源码中,有一个重要的变量modCount,它用于记录集合结构被修改的次数。每当对ArrayList进行结构修改操作(如addremoveclear等操作)时,modCount就会增加。例如,在add方法的源码中:
     public boolean add(E e) {
         ensureCapacityInternal(size + 1);  // 确保内部数组容量足够
         elementData[size++] = e;
         modCount++;
         return true;
     }
  • 同样,在remove方法中也会更新modCount
     public E remove(int index) {
         rangeCheck(index);
         modCount++;
         E oldValue = elementData(index);
         int numMoved = size - index - 1;
         if (numMoved > 0)
             System.arraycopy(elementData, index + 1, elementData, index, numMoved);
         elementData[--size] = null; // 清除引用,帮助GC
         return oldValue;
     }
  • 迭代器的实现与expectedModCount

    • ArrayList的迭代器是通过内部类实现的,例如Itr类。在迭代器的内部,有一个expectedModCount变量,这个变量在迭代器创建时被初始化为集合的modCount。以下是Itr类的部分源码:
     private class Itr implements Iterator<E> {
         int cursor;       // 下一个元素的索引
         int lastRet = -1; // 上一个返回的元素的索引,如果没有则为 - 1
         int expectedModCount = modCount;
         //...
     }
  • 迭代过程中的检查机制

    • 在迭代器的next方法中,会先调用checkForComodification方法来检查modCountexpectedModCount是否相等。如果不相等,就会抛出ConcurrentModificationExceptioncheckForComodification方法的源码如下:
     final void checkForComodification() {
         if (modCount!= expectedModCount)
             throw new ConcurrentModificationException();
     }
  • 例如,在next方法的部分源码中可以看到这个检查:
     @SuppressWarnings("unchecked")
     public E next() {
         checkForComodification();
         int i = cursor;
         if (i >= size)
             throw new NoSuchElementException();
         Object[] elementData = ArrayList.this.elementData;
         if (i >= elementData.length)
             throw new ConcurrentModificationException();
         cursor = i + 1;
         return (E) elementData[lastRet = i];
     }
  • for循环中删除数据报错的原因

    • 当我们使用for - each循环遍历ArrayList时(for - each循环在底层会使用迭代器),如果在循环体中直接使用ArrayListremove方法删除元素,ArrayListmodCount会增加,但迭代器的expectedModCount不变。
    • 例如,以下代码会导致异常:
     ArrayList<Integer> list = new ArrayList<>();
     list.add(1);
     list.add(2);
     list.add(3);
     for (Integer i : list) {
         if (i == 2) {
             list.remove(i);
         }
     }
  • 因为在执行list.remove(i)时,listmodCount被更新,而迭代器的expectedModCount(在迭代器创建时初始化后没有被更新)与modCount不再相等。当迭代器继续执行next操作(for - each循环隐式地调用next操作)时,就会触发checkForComodification方法的检查,抛出ConcurrentModificationException。这就是在for循环内部直接删除数据报错的原因,体现了集合的快速失败机制。