ArrayList 的 Fail Fast 问题

122 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

ArrayList 主要方法

public boolean add(E e) //添加元素到末尾
public boolean isEmpty() //判断是否为空
public int  size()
public E remove(int index) // 删除

FailFast 是什么

https://en.wikipedia.org/wiki/Fail-fast

wiki 百科的说法:

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

fail-fast 机制是java集合(Collection)中能被用来检测错误的一种错误机制。fail-fast 机制并不一定会发生。并发操作时,即当多个线程对同一个集合的内容进行操作, 可能会产生fail-fast事件。

看如下代码:


import java.util.ArrayList;
import java.util.List;

public class FastFailTest {
    public static void main(String[] args) {
        // 构建ArrayList
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        for (int i : list) {
            System.out.println(i);
            list.remove(1);
        }
    }
}

运行结果如下: 在这里插入图片描述

为啥会报错?

for each 循环其实是 java 将代码反编译后,发现 for 循环其实是个迭代器 iterator。 javac 编译后,javap -c 反编译可以看到 第 60 行 可以看到Iterator.hasNext

public class collection.FastFailTest {
  public collection.FastFailTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class java/util/ArrayList
       3: dup
       4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
       7: astore_1
       8: aload_1
       9: iconst_1
      10: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      13: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      18: pop
      19: aload_1
      20: iconst_2
      21: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      24: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      29: pop
      30: aload_1
      31: iconst_3
      32: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      35: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      40: pop
      41: aload_1
      42: iconst_4
      43: invokestatic  #4                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      46: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      51: pop
      52: aload_1
      53: invokeinterface #6,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      58: astore_2
      59: aload_2
      60: invokeinterface #7,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      65: ifeq          99
      68: aload_2
      69: invokeinterface #8,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      74: checkcast     #9                  // class java/lang/Integer
      77: invokevirtual #10                 // Method java/lang/Integer.intValue:()I
      80: istore_3
      81: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
      84: iload_3
      85: invokevirtual #12                 // Method java/io/PrintStream.println:(I)V
      88: aload_1
      89: iconst_1
      90: invokeinterface #13,  2           // InterfaceMethod java/util/List.remove:(I)Ljava/lang/Object;
      95: pop
      96: goto          59
      99: return
}

通过代码可以看到

Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at collection.FastFailTest.main(FastFailTest.java:15)

追踪到这一行

java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)

这个代码,发现是iterator.next()方法中调用的

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

发现方法中对 modCount 和expectedModCount 进行了比较,不相等,会异常 ConcurrentModificationException。

expectedModCount 是 ArrayList 中对一个内部类的成员变量,是随着迭代器创建被初始化,通过迭代器对集合进行操作时,该值才会改变。

继续看代码,删除的核心逻辑如下:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

发现删除的时候,只修改了 modCount, 并没有修改 expectedModCount的内容,这样就不相等了,主要原因是 removeadd 操作后造成 modCount, expectedModCount 不一致,就报错了。

正确的使用方式

通过迭代器删除。

public class FastFailTest {
    public static void main(String[] args) {
        // 构建ArrayList
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
//        for (int i : list) {
//            System.out.println(i);
//            list.remove(1);
//        }

        Iterator<Integer> iterator = list.iterator();

        while (iterator.hasNext()) {
            if (iterator.next() == 1) {
                iterator.remove();
            }
        }
    }
}

原因是因为

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

只要不越界,就会有 expectedModCount = modCount;