fail-fast 机制,即快速失败机制,是 Java 集合(Collection)中的一种错误检测机制,检测在迭代期间集合被修改的情况。fail-fast 机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测 bug。
在集合中,当直接使用 Iterator 迭代(而不是通过 for-each 循环间接使用),对正在被迭代的集合进行结构上的改变(即对该集合使用 add、remove 或 clear 等方法),那么迭代器就不再合法,发生 fail-fast,抛出 ConcurrentModificationException 异常。
import java.util.*; public class Main { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1);list.add(2);list.add(3); Iterator<Integer> iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); list.add(4);// 引起fail-fast } } }
由以上示例,我们通过 ArrayList 中的 Iterator 来剖析 fail-fast 机制原理,分析 ArrayList 中的 Iterator 如何实现 fail-fast 机制,如何抛出 ConcurrentModificationException 异常。
调用 iterator() 获取 Iterator 对象
它会返回一个 Itr 类的对象,而 Itr 类是 ArrayList 的内部类,实现了 Iterator 接口。
public Iterator<E> iterator() { return new Itr();// 返回一个Iterator对象 }
Itr 类的成员变量 expectedModCount
cursor 和 lastRet 都很好理解,我们需要重点关注 expectedModCount,在此之前,我们先搞清楚 modCount 是什么?
modCount 是 ArrayList 的一个成员变量,在 ArrayList 被创建即存在并初始化,modCount 含义为记录从创建后 ArrayList 的修改次数,add、remove 、clear 等方法都会引起 modCount 值的增加。
在创建了 Itr 对象时,将 modCount 储存在数据域 expectedModCount 中,可以理解为保存 ArrayList 当前状态。
private class Itr implements Iterator<E> { int cursor; // 下一个返回元素的索引,开始为0 int lastRet = -1; // 最近返回的元素的索引,没有则为-1 int expectedModCount = modCount; ...... } public E remove(int index) {// ArrayList的remove方法(截取) ...... modCount++;// 每次调用都会增加modCount的值 ...... }
Itr 类的成员函数 checkForComodification()
Itr 类中有一个 checkForComodification() 方法,专门用于检测最新的 modCount 是否等于 expectedModCount,经过前面的分析,可以知道在 ArrayList 进行add,remove,clear 等涉及到修改集合结构的操作时,modCount 就会发生改变(modCount++),所以当使用迭代器时改变集合结构或多线程环境中操作集合,就会使 modCount 发生变化,这样在 checkForComodification 方法中就会抛出 ConcurrentModificationException 异常。
这样即可检测 ArrayList 的状态是否改变过,如果状态改变就抛出 ConcurrentModificationException 异常,这样即实现了fail-fast 机制,通过源码可以看出,每一次 next 和 remove 的调用都会调用 checkForComodification 进行检测。
final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } public E next() { ...... checkForComodification(); ...... } public void remove() { ...... checkForComodification(); ...... }
在单线程的遍历过程中,如果要进行 add、remove 等操作,调用迭代器的 add、remove 方法而不是集合类的 add、remove 方法。
在多线程环境中使用 Java 并发包(java.util.concurrent)中对应的类来代替 ArrayList 和 HashMap。
比如使用 CopyOnWriterArrayList 代替 ArrayList,使用 ConcurrentHashMap 代替 HashMap。