fail-fast and fail-safe

fail-fast

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

1: 代码演示

List list = new ArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
list.add("item4");
Iterator 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 listIterator() {
    return new ListItr(0);
}


public Iterator iterator() {
    return new Itr();
}

private class Itr implements Iterator {
 
   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 list = new CopyOnWriteArrayList<>();
list.add("item1");
list.add("item2");
list.add("item3");
list.add("item4");
Iterator 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 listIterator() {
    return new COWIterator(getArray(), 0);
}

public ListIterator listIterator(int index) {
    Object[] elements = getArray();
    int len = elements.length;
    if (index < 0 || index > len)
        throw new IndexOutOfBoundsException("Index: "+index);

    return new COWIterator(elements, index);
}

static final class COWIterator implements ListIterator {
    /** 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)