foreach循环陷阱、快速失败与安全失败

阿里的 Java 开发手册里会强制不要在 foreach 里进行元素的删除操作

在使用Iterator迭代器遍历并修改集合对象时,有时会抛出异常,例如HashMap、ArrayList等。有时不会抛出异常,例如ConcurrentHashMap等。其中就涉及到了快速失败(fail-fast)和安全失败(fail-safe)。

foreach

for-each 本质上是个语法糖,底层是通过迭代器 Iterator 配合 while 循环实现的。

private class Itr implements Iterator<E> {
    int cursor;             // 下一个元素的索引
    int lastRet = -1;       // 上一个返回元素的索引;如果没有则为 -1
    int expectedModCount = modCount; // ArrayList 的修改次数

    Itr() { }  // 构造函数

    public boolean hasNext() { // 判断是否还有下一个元素
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() { // 返回下一个元素
        checkForComodification(); // 检查 ArrayList 是否被修改过
        int i = cursor; // 当前索引
        Object[] elementData = ArrayList.this.elementData; // ArrayList 中的元素数组
        if (i >= elementData.length) // 超出数组范围
            throw new ConcurrentModificationException(); // 抛出异常
        cursor = i + 1; // 更新下一个元素的索引
        return (E) elementData[lastRet = i]; // 返回下一个元素
    }
}

expectedModCount 被赋值为 modCount,而 modCount 是 ArrayList 中的一个计数器,用于记录 ArrayList 对象被修改的次数。ArrayList 的修改操作包括添加、删除、设置元素值等。每次对 ArrayList 进行修改操作时,modCount 的值会自增 1。在迭代 ArrayList 时,如果迭代过程中发现 modCount 的值与迭代器的 expectedModCount 不一致,则说明 ArrayList 已被修改过,此时会抛出ConcurrentModificationException 异常。

这种机制可以保证迭代器在遍历 ArrayList 时,不会遗漏或重复元素,同时也可以在多线程环境下检测到并发修改问题。

总结:

  • 因为 foreach 循环是基于迭代器实现的,而迭代器在遍历集合时会维护一个 expectedModCount 属性来记录集合被修改的次数。如果在 foreach 循环中执行删除操作会导致 expectedModCount 属性值与实际的 modCount 属性值不一致,从而导致迭代器的 hasNext() 和 next() 方法抛出 ConcurrentModificationException 异常。
  • 为了避免这种情况,应该使用迭代器Iterator 的 remove() 方法来删除元素,该方法会在删除元素后更新迭代器状态,确保循环的正确性。如果需要在循环中删除元素,应该使用迭代器的 remove() 方法,而不是集合自身的 remove() 方法。

快速失败(fail-fast)

在使用迭代器对集合对象进行遍历时,如果A线程正在对集合进行遍历,此时B线程对集合进行修改(增加、删除、修改)操作,或者A线程在遍历过程中对集合进行修改,都会导致A线程抛出ConcurrentModificationException异常。
:可以用Iterator提供的remove()方法安全的删除上一次返回的对象。

为什么在用迭代器遍历集合时,修改集合会抛出异常呢?
原因是迭代器遍历过程中使用了一个modCount变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hasNext()、next()方法遍历 下一个元素之前,都会检测modCount变量是否为exceptedModCount值,是的话就返回遍历;否则抛出异常,终止遍历。

安全失败(fail-safe)

采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制一份原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合所做的修改并不能被迭代器检测到,所以不会抛出异常。

最后总结一下,快速失败和安全失败是对迭代器遍历集合而言的。并发环境下建议使用java.util.concurrent包下的容器类;除非没有修改操作

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值