美文网首页收藏
fail-fast and fail-safe

fail-fast and fail-safe

作者: 蓉漂里的小白 | 来源:发表于2022-02-05 15:51 被阅读0次

    fail-fast

    fail-fast 当有异常或者错误发生时就立即中断执行。字面意思很抽象,其实就是java集合中的一种错误检测机制,当我们在遍历集合元素的时候,如果集合新增或删除元素的话就会抛出异常,防止继续遍历。这就是所谓的快速失败机制。
    常见的集合操作:Hashmap, arrayList

    1: 代码演示

    List<String> list = new ArrayList<>();
    list.add("item1");
    list.add("item2");
    list.add("item3");
    list.add("item4");
    Iterator<String> it = list.listIterator();
    int i = 0;
    while (it.hasNext()) {
        i++;
        System.out.println(it.next());
        if (2 == i) {
            list.add("item5");
        }
    }
    
    image.png

    2: fail-fast机制是如何检测

    通过debug arrayList的源码可以知道,当对arraylist做添加,删除元素时候都会去修改内部的一个成员变量modCount, 并且在遍历arraylist的时候又会去检查modcount和iterator产生的expectedModCount是否相等,不相等则抛出ConcurrentModificationException异常
    ArrayList Add方法源码:

    protected transient int modCount = 0;
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 会修改 modCount
        ....
    }
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
    
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    ArrayList 迭代器的源码

    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }
    
    
    public Iterator<E> iterator() {
        return new Itr();
    }
    
    private class Itr implements Iterator<E> {
     
       int expectedModCount = modCount;
    
        public E next() {
            checkForComodification();
            ...
        }
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
        ...
    }
    

    在list.listIterator() 时就会创建出itr 同时设置exceptedModCount=modCount, 所以当我们只循环中动态的添加了元素就会导致modCount 大于exceptedModCount, 从而抛出ConcurrentModificationException

    fail-safe

    fail-safe 和 fail-fast 是相对的,当有异常或者错误发生时继续执行不会被中断; 其实就是在对集合遍历的时候,如果发生了新增,删除都会在一个复制的集合上进行操作,不会抛出ConcurrentModificationException
    常见的集合操作:CopyOnWriteArrayList, ConcurrentHashMap

    1: 代码演示

    List<String> list = new CopyOnWriteArrayList<>();
    list.add("item1");
    list.add("item2");
    list.add("item3");
    list.add("item4");
    Iterator<String> it = list.listIterator();
    int i = 0;
    while (it.hasNext()) {
        i++;
        System.out.println(it.next());
        if (2 == i) {
            list.add("item5");
        }
    }
    
    image.png

    2: fail-fast机制是如何保障的

    CopyOnWriteArrayList add 方法源码

    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    

    从源码中可以看出来,每次添加一个新的元素都会做一次arrays.copyOf 生成一个新的数组
    CopyOnWriteArrayList 迭代器的源码

    public ListIterator<E> listIterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    
    public ListIterator<E> listIterator(int index) {
        Object[] elements = getArray();
        int len = elements.length;
        if (index < 0 || index > len)
            throw new IndexOutOfBoundsException("Index: "+index);
    
        return new COWIterator<E>(elements, index);
    }
    
    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;
    
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }
    
        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++];
        }
       ………..
    }
    

    从COWIterator源码可以看出来,其实每次迭代的时候都是访问这个快照内的元素,而不是原集合的元素(每次新增的时候都会做一次arrays.copyOf)
    这种设计的好处是保证了在多线程操纵同一个集合的时候,不会因为某个线程修改了集合,而影响其他正在迭代访问集合的线程。缺点是,迭代器不能正确及时的反应集合中的内容,而且一定程度上也增加了内存的消耗。

    相关文章

      网友评论

        本文标题:fail-fast and fail-safe

        本文链接:https://www.haomeiwen.com/subject/zietkrtx.html