详解fail-fast产生的原因
运行以下程序:
import java.util.ArrayList; import java.util.List; import java.util.Iterator; public class Fail { public static void main(String[] args) { List <Integer>list=new ArrayList<>(); list.add(9); list.add(5); list.add(2); list.add(7); Iterator <Integer>iter=list.iterator(); while(iter.hasNext()) { list.add(5); System.out.println(iter.next()); } } }
我们希望看到的时遍历打印list中的数值,并在遍历的过程中,往list中添加数据。
但实际的运行结果却是:
抛出了一个 java.util.ConcurrentModificationException ,这就是我们所说的fail-fast,即快速失败。
那他产生的原因是什么呢?
首先我们找到:java.util.ArrayList$Itr.next ---- 这个是ArrayList 的内部类Itr中的一个方法,它实现了Iterator<E>接口。我们遍历list集合是就是调用的这个方法。
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]; }
可以看到,在第二行有一个: checkForComodification(); 异常就是从这里抛出来的,打开这个方法。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
可以看到 ,该方法比较了modCount , expectedModCount 这两个值,当他们不相等时就会抛出 ConcurrentModifcationException .
通过源码找到 expectedModCount :
他在Itr这个类中定义,当这个类被实例化时,他被初始化成modCount
我们再来找到modCount:
他被定义在AbstractList中,且被初始化为0。前面的英文是对他作用的说明,通过第一段我们就可以知道他的作用:
The number of times this list has been structurally modified.Structural modifications are those that change the size of the list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.
这段话的意思是:这个数记录了这个列表在结构上被修改的次数。结构修改是指改变列表的大小,或者以某种方式干扰列表,使正在进行的迭代可能产生不正确的结果。
在ArrayList中具体哪些操作会改变modCount的值?通过源码可以看到:add()系列,remove()系列,clear(),replace()等,都会使得modCout++ 。(源码就不截出来,大家可以通过JDK源码自己去看)
需要注意的是: list中的set(index,element)等操作并不会改变modCout的值,因此我们一定要深刻理解什么是结构修改。
那么通过以上的探究这个异常产生的原因就非常的清楚了:我们在遍历一个Collection集合时,会调用该类的 .Iterator();则他会建一个Itr的对象,此时Itr类中:expectedModCount就会被初始化为当前的modCount。如果此时我们对Collection集合类进行结构修改时,modCount的值就会改变,而在调用next()时,会先比较这两个值是否相等,此时他们不相等,就抛出了ConcurrentModificationException.
在Itr中,还有remove(),forEachRemaining(Consumer<? super E> action),也会进行checkForComodification(),因此在使用Iterator的时候,都不可以对该集合类进行结构化修改。
java中的语法糖foreach实际是对Iterator进行包装,因此遍历的过程中对集合进行结构化的修改也会抛出这个异常。