Java集合中快速失败机制详解

前言

    以前在看JDK集合源码时,会发现其中有快速失败和失败安全机制的应用实现(以前刚开始看时不知道这个东东是什么意思,后来了解多了才知道这个-_-) 快速失败和失败安全机制其实是一种设计思想,其应用场景不只在Java集合类,很多开源框架里也有提现,Dubbo的集群容错机制,其中除了有快速失败和失败安全机制外,还有失败自动切换、失败自动回复、并行调用多个服务机制。 本人对dubbo框架了解不多,本次只讨论JDK集合内快速失败和失败安全机制的实现。

快速失败机制

    首先看一到面试题:
public static void main (String[] args) {
   ArrayList<Integer> list = new ArrayList<>();
   list.add(1);
   list.add(2);
   for (Integer num : list) {
       if (2 == num) {
           list.remove(num);
       }
   }
   System.out.println(list);
}
    此代码执行结果是什么? 很明显这题其实不难,不会打印,直接抛异常:java.util.ConcurrentModificationException
    根据异常信息,在ArrayList.java:909行抛异常,点进去看会发现:
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}
    当满足条件modCount != expectedModCount,即会抛出ConcurrentModificationException。 那就看看这两个参数是什么意思。 并且在哪种情况下会满足modCount != expectedModCount。

modCount

    看一下modCount字段的备注:
    本人英语不好没法整段翻译,着重看图中圈起来的位置说明其fail-fast(快速失败),并且在add和remove方法内对其自增。
    上图分别是add方法和remove方法里对modCount的自增操作

expectedModCount

    在集合中的一个内部类Itr中,有一个属性expectedModCount,根据字面翻译:预期修改次数
    初始化Itr类对象时会第一次赋值:
int expectedModCount = modCount;
    在Itr对象里调用remove方法是,会再次进行赋值操作。

modCount != expectedModCount

    首先去除Java语法糖机制,看当前类加载后的class文件
    在当前foreach循环删除的场景里,foreach最后会被编译成使用Iterator对象。 这里直接调用集合的remove方法,那么会导致modCount,然而Iterator对象里的expectedModCount属性没有更新,所以在使用Iterator对象获取元素时的前置检测不通过,抛出异常:ConcurrentModificationException。 如果使用Iterator对象的remove方法进行删除元素,会对expectedModCount进行赋值操作,以确保modCount == expectedModCount。并发时需要对Iterator对象进行加锁
注:上诉前置检测抛异常原理其实就是快速失败机制的体现,在迭代器访问集合元素时,如果集合内的元素有改动操作,则立即抛出异常。

失败安全机制

    和快速失败不同,安全失败是调用过程出现了异常,则是不抛出只记录或打印,然后继续执行后续流程。 在java.util.concurrent的并发集合内都是使用安全失败机制,在遍历集合时不是在原集合上进行遍历,而是直接复制一份原集合内容,在复制的集合内容上进行遍历操作 使用复制原集合内容方式能避免快速失败机制,但是会增加内存占用,并且一致性无法保证,只能保证数据的最终一致性。 java.util.concurrent下并发集合可在多线程中并发执行,因为是不会操作原集合内容,所以执行过程中互不影响,都会执行后续流程。
public static void main (String[] args) {
    CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
    list.add(1);
    list.add(2);
    final Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()) {
        Integer next = iterator.next();
        if (2 == next) {
            list.remove(next);
        }
    }
    System.out.println(list);
}
    这里会正常打印结果

最后

    如果上述表达有任何问题,欢迎指出! 最后虚心学习,共同进步 -_-
经验分享 程序员 微信小程序 职场和发展