fail-fast 与 fail-safe 机制有什么区别

快速失效与安全失效,是针对迭代数据结构过程出现的两种说法。

Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。

以ArraryList举例,何时出现fail-fast事件,抛出ConcurrentModificationException异常。

public abstract class AbstractListextends AbstractCollectionimplements List {

...

// AbstractList中唯一的属性

    // 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1

    protected transient int modCount =0;

    // 返回List对应迭代器。实际上,是返回Itr对象。

    public Iteratoriterator() {

return new Itr();

    }

// Itr是Iterator(迭代器)的实现类

    private class Itrimplements Iterator {

int cursor =0;

        int lastRet = -1;

        // 修改数的记录值。

        // 每次新建Itr()对象时,都会保存新建该对象时对应的modCount;

        // 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等;

        // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。

        int expectedModCount =modCount;

        public boolean hasNext() {

return cursor != size();

        }

public E next() {

// 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等;

            // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。

            checkForComodification();

            try {

E next = get(cursor);

                lastRet =cursor++;

                return next;

            }catch (IndexOutOfBoundsException e) {

checkForComodification();

                throw new NoSuchElementException();

            }

}

public void remove() {

if (lastRet == -1)

throw new IllegalStateException();

            checkForComodification();

            try {

AbstractList.this.remove(lastRet);

                if (lastRet

cursor--;

                lastRet = -1;

                expectedModCount =modCount;

            }catch (IndexOutOfBoundsException e) {

throw new ConcurrentModificationException();

            }

}

final void checkForComodification() {

if (modCount !=expectedModCount)

throw new ConcurrentModificationException();

        }

}

...

}

在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

在创建iterator的时候,modCount 与 expectedModCount 是相等的,但是何时会被修改呢?下面可以看一下ArraryList的源码。

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable {

...

// list中容量变化时,对应的同步函数

    public void ensureCapacity(int minCapacity) {

modCount++;

        int oldCapacity = elementData.length;

        if (minCapacity > oldCapacity) {

Object oldData[] = elementData;

            int newCapacity = (oldCapacity *3) /2 +1;

            if (newCapacity < minCapacity)

newCapacity = minCapacity;

            // minCapacity is usually close to size, so this is a win:

            elementData = Arrays.copyOf(elementData, newCapacity);

        }

}

// 添加元素到队列最后

    public boolean add(E e) {

// 修改modCount

        ensureCapacity(size +1);  // Increments modCount!!

        elementData[size++] = e;

return true;

    }

// 添加元素到指定的位置

    public void add(int index, E element) {

if (index > size || index <0)

throw new IndexOutOfBoundsException(

"Index: " + index +", Size: " + size);

        // 修改modCount

        ensureCapacity(size +1);  // Increments modCount!!

        System.arraycopy(elementData, index, elementData, index +1,

                size - index);

        elementData[index] = element;

        size++;

    }

// 添加集合

    public boolean addAll(Collection c) {

Object[] a = c.toArray();

        int numNew = a.length;

        // 修改modCount

        ensureCapacity(size + numNew);  // Increments modCount

        System.arraycopy(a, 0, elementData, size, numNew);

        size += numNew;

        return numNew !=0;

    }

// 删除指定位置的元素

    public E remove(int index) {

RangeCheck(index);

        // 修改modCount

        modCount++;

        E oldValue = (E) elementData[index];

        int numMoved = size - index -1;

        if (numMoved >0)

System.arraycopy(elementData, index +1, elementData, index, numMoved);

        elementData[--size] =null; // Let gc do its work

        return oldValue;

    }

// 快速删除指定位置的元素

    private void fastRemove(int index) {

// 修改modCount

        modCount++;

        int numMoved = size - index -1;

        if (numMoved >0)

System.arraycopy(elementData, index +1, elementData, index,

                    numMoved);

        elementData[--size] =null; // Let gc do its work

    }

// 清空集合

    public void clear() {

// 修改modCount

        modCount++;

        // Let gc do its work

        for (int i =0; i < size; i++)

elementData[i] =null;

        size =0;

    }

...

}

通过代码我们可以看到:只要更改集合中的元素个数:例如add(),remove(),clear()都会改变ModCount的值。

接下来,我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:

(01) 新建了一个ArrayList,名称为arrayList。

(02) 向arrayList中添加内容。

(03)新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值

(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。

(05) 之后,就会产生fail-fast事件了,即:“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。

       在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1

“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

如何避免fail-fast问题的产生呢?java.util.current包帮你解决。看一下CopyOnWriteArrayList

public class CopyOnWriteArrayList

implements List, RandomAccess, Cloneable, java.io.Serializable {

...

// 返回集合对应的迭代器

    public Iteratoriterator() {

return new COWIterator(getArray(), 0);

    }

...

private static class COWIteratorimplements ListIterator {

private final Object[]snapshot;

        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {

cursor = initialCursor;

            // 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。

            // 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。

            snapshot = elements;

        }

public boolean hasNext() {

return cursor

        }

public boolean hasPrevious() {

return cursor >0;

        }

public E next() {

if (!hasNext())

throw new NoSuchElementException();

            return (E)snapshot[cursor++];

        }

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

        }

}

...

}

(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。

(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。

(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常。



个人公号:【排骨肉段】,可以关注一下。

推荐阅读更多精彩内容