fail-fast 机制是什么?(详解)

1. 介绍:

fail-fast:快速失败系统,通常设计用于停止有缺陷的过程,这是一种理念,在进行系统设计时优先考虑异常情况,一旦发生异常,直接停止并上报。

举一个最简单的fail-fast例子:

public int divide(int divisor, int dividend){
    
    
    if (dividend == 0) {
    
    
        throw new RuntimeException("被除数不能为0");    //这里就是fail-fast的体现
    }
    return divisor / dividend;
}

上述代码中一旦发现被除数是0,那么直接抛出异常,并明确提示异常原因,这就是fail-fast的应用。

这样做一方面是为了避免执行接下来复杂的代码,另一方面可以根据错误原因进行针对性处理。

fail-fast其实在日常代码中处处可以看见它的身影,好处上面也提到了。但是,fail-fast在一些场景下可能会出现意想不到的问题。

2. 集合类中的fail-fast

我们通常说的Java中的fail-fast机制,默认指的是Java集合中的一种错误检测机制,当多个线程对集合进行结构性的改变时,有可能会出发fail-fast机制,这个时候会抛出ConcurrentModificationException异常。很多时候单线程环境下也会出发这个异常,导致我们摸不着头脑,接下来具体分析一下:

当使用foreach遍历集合时对集合元素进行add / remove时,就会抛出ConcurrentModificationException。

for(String str : list){
    
    
    if(str.equals("haha")){
    
    
        list.remove(str);   //抛出异常
    }
}

在对错误分析之前,我们先对foreach进行解语法糖,进行反编译,可以得到以下代码:

Iterator iterator = userNames.iterator();
do
{
    
    
    if(!iterator.hasNext())
        break;
    String userName = (String)iterator.next();
    if(userName.equals("Jobs"))
        userNames.remove(userName);
} while(true);

可以看到,foreach底层实际上是利用iterator和while循环实现的。

扫描二维码关注公众号,回复: 14619850 查看本文章

发生异常的的代码是iterator的next方法,该方法中调用了checkForComodification()方法:

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

可以发现,问题出现在于当modCount 和 expectedModCount 不相等时,就会抛出异常。那么什么是modCount ,什么是expectedModCount ,为什么remove操作会导致这两个变量不相等呢?

3. 原因分析

modCount是ArrayList中的一个成员变量,它表示集合实际被修改的次数,当ArrayList被创建时就存在了,初始值为0。

expectedModCount 是iterator中的一个成员变量,而iterator是ArrayList的一个内部类,当ArrayList调用iterator()方法获取一个迭代器时,会创建一个iterator,并且将expectedModCount 初始化为modCount的值。只有该迭代器修改了集合,expectedModCount 才会修改。

那么,接下来我们来分析,remove操作发生了什么?

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
}

可以发现,集合的remove操作只修改了modCount,而没有修改expectedModCount ,这就导致了modCount和expectedModCount 不一致。

4. 解决办法

那么如果我们想要在循环遍历中删除元素呢,该咋办?

解决办法:使用iterator遍历,使用iterator的remove方法删除元素。

Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
    
    
    String str = iterator.next();
    iterator.remove();   //正确做法
}

iterator的remove方法首先会调用集合list的remove方法,导致modCount++,接下来会执行expectedModCount = modCount,确保expectedModCount 和 modCount相同。

5. 总结

总结:之所以会抛出ConcurrentModificationException异常,是因为foreach底层是使用iterator来遍历,但是循环中元素的添加或者删除却是调用集合本身的方法,导致iterator在遍历过程中,发现有元素在自己不知不觉的情况下添加/删除了,就会抛出异常,告知用户可能会并发修改问题!

所以在循环中如果想要进行remove操作的话,务必使用iterator方式。另外,当出现ConcurrentModificationException,优先考虑fail-fast相关的情况,上面的例子其实并没有真正发生并发,而是fail-fast的保护机制。

猜你喜欢

转载自blog.csdn.net/OYMNCHR/article/details/124515536
今日推荐