zoukankan      html  css  js  c++  java
  • 浅谈fail-fast机制

    fail-fast机制即为快速失败机制,个人认为是一种防护措施,在集合结构发生改变的时候,使尽全力抛出ConcurrentModificationException,所以该机制大部分用途都是用来检测Bug的;

    下面的代码可以引发fail-fast

     1     public static void main(String[] args) {
     2         List<String> list = new ArrayList<>();
     3         for (int i = 0 ; i < 10 ; i++ ) {
     4             list.add(i + "");
     5         }
     6         Iterator<String> iterator = list.iterator();
     7         int i = 0 ;
     8         while(iterator.hasNext()) {
     9             if (i == 3) {
    10                 list.remove(3);
    11                 //list.add("11");   添加元素同样会引发
    12             }
    13             System.out.println(iterator.next());
    14             i ++;
    15         }
    16     }

    fail-fast原理

    每个集合都会实现可遍历的接口,以上述代码为例,集合调用iterator();方法的时候,其实是返回了一个new Itr();

        /**
         * Returns an iterator over the elements in this list in proper sequence.
         *
         * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
         *
         * @return an iterator over the elements in this list in proper sequence
         */
        public Iterator<E> iterator() {
            return new Itr();
        }

    以下是Itr源码

        /**
         * An optimized version of AbstractList.Itr
         */
        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;
    
            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
            @SuppressWarnings("unchecked")
            public void forEachRemaining(Consumer<? super E> consumer) {
                Objects.requireNonNull(consumer);
                final int size = ArrayList.this.size;
                int i = cursor;
                if (i >= size) {
                    return;
                }
                final Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length) {
                    throw new ConcurrentModificationException();
                }
                while (i != size && modCount == expectedModCount) {
                    consumer.accept((E) elementData[i++]);
                }
                // update once at end of iteration to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }

    Itr有3个重要属性;

    cursor是指集合遍历过程中的即将遍历的元素的索引

    lastRet是cursor -1,默认为-1,即不存在上一个时,为-1,它主要用于记录刚刚遍历过的元素的索引。

    expectedModCount它初始值就为ArrayList中的modCount(modCount是抽象类AbstractList中的变量,默认为0,而ArrayList 继承了AbstractList ,所以也有这个变量,modCount用于记录集合操作过程中作的修改次数)

    由源码可以看出,该异常就是在调用next()的时候引发的,而调用next()方法的时候会先调用checkForComodification(),该方法判断expectedModCount与modCount是否相等,如果不等则抛异常了

    那么问题就来了,初始化的时候expectedModCount就被赋值为modCount,而且源码当中就一直没有改变过,所以肯定是modCount的值变了

    arrayList继承了abstractList,abstractList有modCount属性,通过以下源码我们可以看到,当ArrayList调用add、remove方法,modCount++

        /**
         * Inserts the specified element at the specified position in this
         * list. Shifts the element currently at that position (if any) and
         * any subsequent elements to the right (adds one to their indices).
         *
         * @param index index at which the specified element is to be inserted
         * @param element element to be inserted
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public void add(int index, E element) {
            rangeCheckForAdd(index);
    
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    
        /**
         * Removes the element at the specified position in this list.
         * Shifts any subsequent elements to the left (subtracts one from their
         * indices).
         *
         * @param index the index of the element to be removed
         * @return the element that was removed from the list
         * @throws IndexOutOfBoundsException {@inheritDoc}
         */
        public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }

    所以由此可见,对集合的操作中若modCount发生了改变,则会引发fail-fast机制;同时可以看出如果想要移除集合某元素,可以使用迭代器的remove方法,则不会引发fail-fast;

    发表该文章也参考了许多另一片文章的内容,详情地址:https://blog.csdn.net/zymx14/article/details/78394464

  • 相关阅读:
    流式布局思想 js函数的几种简写方式 面向对象 js vue引入bootstrap和jQuery环境
    04--CBV源码分析 Django的settings源码分析 模板层
    C#使用委托和事件来重写串口的接收数据方法DataReceived方法完成数据的接收处理
    自定义控件的封装、常用的鼠标事件的重载、定时器的使用、event事件以及事件过滤器、文件操作以及文本流和数据流的使用
    QT的优点、项目文件目录、main函数、QpushButton、对象树、Qt中坐标系、Qt中信号和槽、自定义信号和槽、信号和槽的拓展、Qt4版本中的信号和槽的缺点、Lambda表达式
    Stylet框架显示自定义界面的步骤
    MahApps.Metro使用
    async和await的使用
    第一个WPF程序(串口调试)
    结构、类、属性:以及面向对象的各种特性继承、封装、多态
  • 原文地址:https://www.cnblogs.com/pengx/p/9576613.html
Copyright © 2011-2022 走看看