Java,你告诉我 fail-fast 是什么鬼?(1)

简介: Java,你告诉我 fail-fast 是什么鬼?

01、前言


说起来真特么惭愧:十年 IT 老兵,Java 菜鸟一枚。今天我才了解到 Java 还有 fail-fast 一说。不得不感慨啊,学习真的是没有止境。只要肯学,就会有巨多巨多别人眼中的“旧”知识涌现出来,并且在我这全是新的。


能怎么办呢?除了羞愧,就只能赶紧全身心地投入学习,把这些知识掌握。


为了镇楼,必须搬一段英文来解释一下 fail-fast。


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.

大家不嫌弃的话,我就用蹩脚的英语能力翻译一下。某场战役当中,政委发现司令员在乱指挥的话,就立马报告给权限更高的中央军委——这样可以有效地避免更严重的后果出现。当然了,如果司令员是李云龙的话,报告也没啥用。


不过,Java 的世界里不存在李云龙。fail-fast 扮演的就是政委的角色,一旦报告给上级,后面的行动就别想执行。


怎么和代码关联起来呢?看下面这段代码。


public void test(Wanger wanger) { 
  if (wanger == null) {
  throw new RuntimeException("wanger 不能为空");
  }
  System.out.println(wanger.toString());
}


一旦检测到 wanger 为 null,就立马抛出异常,让调用者来决定这种情况下该怎么处理,下一步 wanger.toString() 就不会执行了——避免更严重的错误出现,这段代码由于太过简单,体现不出来,后面会讲到。


瞧,fail-fast 就是这个鬼,没什么神秘的。如果大家源码看得比较多的话,这种例子多得就像旅游高峰期的人头。


然后呢,没了?三秒钟,别着急,我们继续。


02、for each 中集合的 remove 操作


很长一段时间里,我都不明白为什么不能在 for each 循环里进行元素的 remove。今天我们就来借机来体验一把。

List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
for (String str : list) {
  if ("沉默王二".equals(str)) {
    list.remove(str);
  }
}
System.out.println(list);


这段代码看起来没有任何问题,但运行起来就糟糕了。


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 com.cmower.java_demo.str.Cmower3.main(Cmower3.java:14)



为毛呢?


03、分析问题的杀手锏


这时候就只能看源码了,ArrayList.java 的 909 行代码是这样的。


final void checkForComodification() {

   if (modCount != expectedModCount)

       throw new ConcurrentModificationException();

}



也就是说,remove 的时候执行了 checkForComodification 方法,该方法对 modCount 和 expectedModCount 进行了比较,发现两者不等,就抛出了 ConcurrentModificationException 异常。


可为什么会执行 checkForComodification 方法呢?这就需要反编译一下 for each 那段代码了。

List<String> list = new ArrayList();
list.add("沉默王二");
list.add("沉默王三");
list.add("一个文章真特么有趣的程序员");
Iterator var3 = list.iterator();
while (var3.hasNext()) {
  String str = (String) var3.next();
  if ("沉默王二".equals(str)) {
  list.remove(str);
  }
}
System.out.println(list);



原来 for each 是通过迭代器 Iterator 配合 while 循环实现的。


1)ArrayList.iterator() 返回的 Iterator 其实是 ArrayList 的一个内部类 Itr。


public Iterator<E> iterator() {

   return new Itr();

}



Itr 实现了 Iterator 接口。


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() {}
    public boolean hasNext() {
        return cursor != size;
    }
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
}



也就是说 new Itr() 的时候 expectedModCount 被赋值为 modCount,而 modCount 是 List 的一个成员变量,表示集合被修改的次数。由于 list 此前执行了 3 次 add 方法,所以 modCount 的值为 3;expectedModCount 的值也为 3。


可当执行 list.remove(str) 后,modCount 的值变成了 4。


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 方法内部调用了 fastRemove 方法。


下一次循环执行到 String str = (String) var3.next(); 的时候,就会调用 checkForComodification 方法,此时一个为 3,一个为 4,就只好抛出异常 ConcurrentModificationException 了。


不信,可以直接在 ArrayList 类的 909 行打个断点 debug 一下。

image.png



真的耶,一个是 4 一个是 3。


总结一下。在 for each 循环中,集合遍历其实是通过迭代器 Iterator 配合 while 循环实现的,但是元素的 remove 却直接使用的集合类自身的方法。这就导致 Iterator 在遍历的时候,会发现元素在自己不知情的情况下被修改了,它觉得很难接受,就抛出了异常。


读者朋友们,你们是不是觉得我跑题了,fail-fast 和 for each 中集合的 remove 操作有什么关系呢?


有!Iterator 使用了 fail-fast 的保护机制。


相关文章
|
2月前
|
安全 Java 程序员
深入Java集合框架:解密List的Fail-Fast与Fail-Safe机制
本文介绍了 Java 中 List 的遍历和删除操作,重点讨论了快速失败(fail-fast)和安全失败(fail-safe)机制。通过普通 for 循环、迭代器和 foreach 循环的对比,详细解释了各种方法的优缺点及适用场景,特别是在多线程环境下的表现。最后推荐了适合高并发场景的 fail-safe 容器,如 CopyOnWriteArrayList 和 ConcurrentHashMap。
60 5
|
安全 Java UED
深入刨析Java-ArrayList的Fail-Fast机制
本文将深入剖析Fail-Fast机制的原理,并结合代码示例演示如何正确处理并发修改问题,确保Java应用程序的稳定性和可靠性。
|
Java
【Java】从源码分析fail-fast和fail-safe是如何产生的
【Java】从源码分析fail-fast和fail-safe是如何产生的
81 0
|
Java API
Java 集合框架04-fail-fast总结
上一篇我们介绍了ArrayList的相关源码,这篇我们将了解一下fail-fast机制的相关知识
105 0
Java 集合框架04-fail-fast总结
|
安全 Java Android开发
java集合系列(4)fail-fast(面试常问)
今天来看java集合中一个常见的错误机制fail-fast机制。出现在这个错误机制的本质就是因为单线程和多线程的不同。下面就好好看一下这个机制是怎么是出现的。
156 0
java集合系列(4)fail-fast(面试常问)
Java fail-fast 机制
fail-fast 机制,即快速失败机制,是 Java 集合(Collection)中的一种错误检测机制,检测在迭代期间集合被修改的情况。fail-fast 机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测 bug。
Java fail-fast 机制
|
Java 程序员
Java,你告诉我 fail-fast 是什么鬼?
说起来真特么惭愧:十年 IT 老兵,Java 菜鸟一枚。今天我才了解到 Java 还有 fail-fast 一说。不得不感慨啊,学习真的是没有止境。只要肯学,就会有巨多巨多别人眼中的“旧”知识涌现出来,并且在我这全是新的。 能怎么办呢?除了羞愧,就只能赶紧全身心地投入学习,把这些知识掌握。
Java,你告诉我 fail-fast 是什么鬼?
|
Java 程序员
Java,你告诉我 fail-fast 是什么鬼?(2)
Java,你告诉我 fail-fast 是什么鬼?
125 0
|
安全 Java 应用服务中间件
深入理解Java中的fail-fast和fail-safe
什么是快速失败(fail-fast)和安全失败(fail-safe)?它们又和什么内容有关系。以上两点就是这篇文章的内容,废话不多话,正文请慢用。
1816 0
|
Java 索引
Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)
概要 前面,我们已经学习了ArrayList。接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解。内容包括::1 fail-fast简介2 fail-fast示例3 fail-fast解决办法4 fail-fast原理5 解决fail-fast的原理 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308762.html    1 fail-fast简介 fail-fast 机制是java集合(Collection)中的一种错误机制。
1409 0