快速失败是指某个线程在迭代集合类的时候,不允许其他线程修改该集合类的内容,这样迭代器迭代出来的结果就会不准确。
比如用iterator迭代collection的时候,iterator就是另外起的一个线程,它去迭代collection,如果此时用collection.remove(obj)这个方法修改了collection里面的内容的时候,就会出现ConcurrentModificationException异常,这时候该迭代器就快速失败。
代码如下:
package com.vino.assemble; import java.util.ArrayList; import java.util.List; /** * show fail fast in in the hashmap * @author haowei.yu * */ public class FailFastDemo { public static void main(String[] args) { List<Integer> tempList = new ArrayList<Integer>(); tempList.add(1); tempList.add(2); tempList.add(3); tempList.add(4); tempList.add(5); tempList.add(1); //use iterator to travel the list and remove the value equals to 1 for(int temp: tempList){ if(temp == 1){ tempList.remove(temp); } } } }
这样运行程序,就会抛出java.util.ConcurrentModificationException异常。
这样做的好处是:对于非并发集合,在用迭代器对其迭代时,若有其他线程修改了增减了集合中的内容,这个迭代会马上感知到,并且立即抛出 ConcurrentModificationException 异常,而不是在迭代完成后或者某些并发的场景才报错。具体这样的机制在java里是怎么实现的呢?其实很简单,是通过modCount域来实现的。modCount顾名思义是修改次数,对List内容的修改都会增加这个值,在迭代器初始化的过程中会将这个值赋值给迭代器的expectedModCount。我们来看ArrayList和Iterator的源码,注释已经写的听清楚了,这里就不解释了。
//ArrayList的源码 protected transient int modCount = 0; //In AbstractList class,provider fail-fast iterators.note that this field is transient public boolean add(E e) { modCount++; // Increments modCount!! ..... return true; } public E remove(int index) { .... modCount++; // Increments modCount!! .... return oldValue; } //Iterator的源码 int expectedModCount = modCount; // iterator believes the backing list should hava. public void remove() { try { .... if (modCount != expectedModCount)// judge the modCount equals with expectedModCount. throw new ConcurrentModificationException(); ... } }
TIPS:
- 在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。
- 迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何的保证。快速失败迭代器只是尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。