Java fail-fast 机制

简介

  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();
	......
}

避免 fail-fast

单线程环境

  在单线程的遍历过程中,如果要进行 add、remove 等操作,调用迭代器的 add、remove 方法而不是集合类的 add、remove 方法。

多线程环境

  在多线程环境中使用 Java 并发包(java.util.concurrent)中对应的类来代替 ArrayList 和 HashMap。
  比如使用 CopyOnWriterArrayList 代替 ArrayList,使用 ConcurrentHashMap 代替 HashMap。

posted @ 2022-01-23 01:51  Acx7  阅读(77)  评论(0编辑  收藏  举报