源码详解Java快速失败机制fail-fast

示例

public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        for (Integer num : list) {
            if (2 == num) {
                list.remove(num);
            }
        }
        System.out.println(list);
}

先看一段代码。
第909行抛出了异常。


再看看909行,相关方法。

在这里插入图片描述

源码

把上述代码编译的class文件取出来。
在这里插入图片描述

Java的语法糖在编译期间,把for(;;)语法编译成了Java中的迭代器iterator(),这也是抛出异常的主要原因。

ArrayList<Integer> list = new ArrayList();

先new了一个集合对象ArrayList,在ArrayList源码中的第601行,有一个protected修饰的变量modCount,在集合创建之后,该变量的初始值为0。

protected transient int modCount = 0;

记住这个变量,接着向这个集合中添加了两个元素,执行了add()方法,看下add()方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

// add()方法中执行了ensureCapacityInternal()方法,这一步的作用是判断add之后容器是否需要扩容。
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 又执行了ensureExplicitCapacity()方法,该方法中,modCount执行了++操作
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

在每一次add方法中,modCount的值+1

在添加了两个元素之后,modCount的值变成了2

再看main方法的下一行

Iterator var2 = list.iterator();

执行了iterator()方法

public Iterator<E> iterator() {
	return new Itr();
}

在该方法中,new了一个Itr对象

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    
    ···
}

Itr是ArrayList的内部类,它实现了Java中的迭代器Iterator类,并且实现了Iterator类中的hasNext,next,remove基本方法。

在创建Itr的过程中,modCount变量被赋给了Itr中的expectedModCount

此时 expectedModCount = modCount = 2

再看main的下一行

while(var2.hasNext()) {
    Integer num = (Integer)var2.next();
    if (2 == num) {
    	list.remove(num);
    }
}

编译后的源码中,使用hasNext方法来判断是否已经遍历到数组中的最后一位,而next方法用来移动数组中的指针,帮助我们遍历集合中的元素。

看看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];
}

next()中执行了checkForComodification()方法。

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

该方法比较了一次modCountexpectedModCount的值是否相等,若不相等,抛出异常。

遍历第一个元素时,没有问题,第一个元素是1,没有走我们的list.remove()方法。

循环第二遍的时候,2==num,开始执行list.remove(num)方法。

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; // clear to let GC do its work

    return oldValue;
}

看看ArrayList中的remove()方法,remove操作时,modCount执行了++操作。

此时modCount由2变成了3。

继续循环,当程序进入

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

方法时,modCount已经和expectedModCount不相等了。至此抛出并发修改异常。

这就是Java集合中的快速失败机制。

根本原因就在于,forEach循环的语法会被编译成使用Iterator对象,而Iterator对象每次会调用next()方法,由于遍历过程中一些增删的操作导致ArrayList的modCount的增加,在Iterator的next()方法判断时,expectedModCount并没有被更新,检测不通过。

解决方案

最常用的解决方案,是使用fori或forr的遍历形式,但当正向遍历时,如果执行的是删除操作,则删除之后,ArrayList会调用System.arraycopy(elementData, index+1, elementData, index, numMoved)方法来重新规划集合的大小,很有可能导致数组下标越界异常。

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; // clear to let GC do its work

    return oldValue;
}

使用反向遍历可以避免这个问题,因为数组从后往前遍历,很难出现数组下标越界。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值