Iterator_FailFast_FailSafe源码解析

Iterator_FailFast_FailSafe源码解析

一、源码演示

public class FailFastVsFailSafe {
    // fail-fast 一旦发现遍历的同时其它人来修改,则立刻抛异常
    // fail-safe 发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历运行完成

    private static void failFast() {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("A"));
        list.add(new Student("B"));
        list.add(new Student("C"));
        list.add(new Student("D"));
        for (Student student : list) {
            System.out.println(student);
        }
        System.out.println(list);
    }

    private static void failSafe() {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");
        list.add("D");
        for (String li : list) {
            System.out.println(li);
        }
        System.out.println(list);
    }


   static class Student{
        private String name;

        public Student(String name) {
            this.name=name;
        }

       public Student() {
       }


       public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }


       @Override
       public String toString() {
           return "Student{" +
                   "name='" + name + '\'' +
                   '}';
       }
   }

    public static void main(String[] args) {
        failFast();
      //  failSafe();
    }
}

1.failFast

​ 我们在打印是给断点加情况,当遍历到C的时候断点停下来。然后在断点里加入E。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

放行debug,显示并发修改异常。这是为什么呢?

一旦发现遍历的同时其它人来修改,就会抛出并发修改异常。这种行为就叫fail-fast.

在这里插入图片描述

2.failSafe

​ 重复以上过程,我们对failSafe做遍历过程中的并发修改。如下图,并没有抛出异常。这种允许遍历的同时进行并发修改的行为就是failSafe。

​ 在发现遍历的同时其它人来修改,应当能有应对策略,例如牺牲一致性来让整个遍历运行完成

在这里插入图片描述

那么ArrayList和CopyWriteArrayList如何分别保证failFast和failSafe的行为呢?接下来我们来解析一下源码。

二、源码解读

1.failFast

​ 首先我们先给增强for循环打个断点。我们知道增强for循环的底层是迭代器,在第一次循环时会创建一个迭代器。我们force step into 进去看一下,可以看到会先创建一个迭代器对象。

在这里插入图片描述

​ 点进去看一下,部分源码如下:

    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        // prevent creating a synthetic constructor
        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i < size) {
                final Object[] es = elementData;
                if (i >= es.length)
                    throw new ConcurrentModificationException();
                for (; i < size && modCount == expectedModCount; i++)
                    action.accept(elementAt(es, i));
                // update once at end to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

源码解析如下:

  1. cursor`:记录下一个要返回的元素的索引。
  2. lastRet:记录上一个返回的元素的索引,初始值为 -1。
  3. expectedModCount:记录迭代器创建时的 modCount 值,用于检测并发修改。
  4. hasNext():判断是否还有下一个元素,通过比较 cursorsize 的值来确定。
  5. next():返回下一个元素,并更新 cursorlastRet
  6. remove():移除上一个返回的元素,通过调用 ArrayListremove() 方法实现,并更新相应的变量。
  7. forEachRemaining():接收一个 Consumer 参数,对剩余的元素应用给定的操作。在遍历过程中,会检查并发修改。
  8. checkForComodification():用于检查是否有并发修改,通过比较 modCountexpectedModCount 的值来判断。

总体而言,Itr 类实现了迭代器的基本功能,包括遍历、移除和并发修改检测等。它与 ArrayList 类紧密结合,通过访问 ArrayList 的内部变量和方法来实现迭代操作。

我们来总结一下这个流程:

​ 1.首先增强for循环在第一次循环时会创建一个迭代器对象。

​ 2.接下来,他会调用构造器构造方法,然后初始化迭代器成员变量。

​ 3.然后会调用hasNext()和next()方法去进行遍历。在next()方法中会调checkForComodification()方法用于检查是否有并发修改。

​ 4.具体是通过比较 List的修改次数modCount 和迭代器的修改次数 expectedModCount 的值来判断,如果不等就抛出并发修改异常。

​ 这就是ArrayList类failFast这一行为的整个过程了。

2.failSafe

接下来我们来看一下failSafe的源码,同样打断点进入。可以看到会新建一个COW迭代器,跟进去看一下。

在这里插入图片描述

源码如下:

    static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;

        COWIterator(Object[] es, int initialCursor) {
            cursor = initialCursor;
            snapshot = es;
        }

        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        public boolean hasPrevious() {
            return cursor > 0;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (! hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            if (! hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            final int size = snapshot.length;
            int i = cursor;
            cursor = size;
            for (; i < size; i++)
                action.accept(elementAt(snapshot, i));
        }
    }

源码分析如下:

  1. snapshot:存储了一个 CopyOnWriteArrayList 的快照,即迭代器创建时的数组状态。
  2. cursor:表示下一个要返回的元素的索引。
  3. COWIterator 构造方法:接收一个数组 es 和初始的 cursor 值,用于初始化迭代器的状态。
  4. hasNext():判断是否还有下一个元素,通过比较 cursorsnapshot.length 的值来确定。
  5. hasPrevious():判断是否还有上一个元素,通过比较 cursor 和 0 的值来确定。
  6. next():返回下一个元素,并将 cursor 值加 1。
  7. previous():返回上一个元素,并将 cursor 值减 1。
  8. nextIndex():返回下一个元素的索引。
  9. previousIndex():返回上一个元素的索引。
  10. remove()set()add():这些操作都抛出 UnsupportedOperationException 异常,表示不支持对快照进行修改。
  11. forEachRemaining():接收一个 Consumer 参数,对剩余的元素应用给定的操作。在遍历过程中,将 cursor 设置为 snapshot.length,避免后续的迭代操作。

总体而言,COWIterator 类实现了 ListIterator 的基本功能,提供了迭代访问 CopyOnWriteArrayList 的元素。由于 CopyOnWriteArrayList 的迭代器操作的是一个快照,因此不支持对快照进行修改操作,并且在遍历过程中保持了对快照的一致性。

总结一下流程:

  1. 首先创建一个迭代器:在执行增强型 for 循环之前,会通过调用 CopyOnWriteArrayListiterator() 方法创建一个迭代器。
  2. 获取迭代器的快照:迭代器内部会对 CopyOnWriteArrayList 进行快照,即复制一份当前的数组状态作为迭代器的数据源。
  3. 迭代器遍历:迭代器通过 hasNext()next() 方法依次遍历快照中的元素。这些方法是基于快照进行操作,因此对 CopyOnWriteArrayList 的修改不会影响迭代器的遍历过程。
  4. 最后完成遍历:当迭代器遍历完所有元素后,循环结束。
  5. 并发修改检测:由于迭代器遍历的是快照而非原始数组,所以在遍历期间对 CopyOnWriteArrayList 的修改不会引发 ConcurrentModificationException 异常。这是 fail-safe 策略的特点。

注意: 在增强型 for 循环中,迭代器是只读的,因此不支持在循环中使用 remove()set()add() 等方法对集合进行修改操作。如果尝试在循环中进行修改操作,将会抛出 UnsupportedOperationException 异常。

总结:

1.failFast

  • ArrayList是fail-fast的典型代表,一旦发现遍历的同时其他人来修改,则立即抛异常。

  • 实现原理:记录了循环开始时的次数,如果在循环的过程中修改次数被改,则会尽快失败,抛出异常,阻止循环继续。

2.failSafe

  • CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离。

  • 实现原理:读写分离。遍历时使用旧数组,在元素增加时创建一个新数组,长度是旧数组长度+1,然后将旧数组的元素copy到新数组中。添加是一个数组,遍历是另一个数组,互不干扰。遍历结束后回收旧数组,替换成新数组,于是在下次遍历的时候就遍历新的(添加后的)数组。

ArrayList是fail-fast的典型代表,一旦发现遍历的同时其他人来修改,则立即抛异常。

  • 实现原理:记录了循环开始时的次数,如果在循环的过程中修改次数被改,则会尽快失败,抛出异常,阻止循环继续。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值