NOTE

5.26 fail-fast

fail-fast and fail-safe, Java iterator design, examples, implementation analysis, and summary.

JavaCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. fail-fast and fail-safe

fail-fast: if a system immediately stops execution when an exception or error occurs, this design is called fail-fast.

fail-safe: if a system can continue executing when a certain exception or error occurs instead of being interrupted, this design is called fail-safe.

2. Java Iterator Design

A for-each loop is also implemented using an iterator underneath.

public class ForEarch
{
    public static void main(String[] args)
    {
        List<String> stringList = Arrays.asList("A", "B", "C");
        for (String s : stringList)
        {
            System.out.println(s);
        }
    }
}
  • View the bytecode as shown below:

3. Examples

3.1. fail-fast List

public class ConcurrentModificationExceptionTest3
{
    public static void main(String[] args)
    {
        List<String> stringList = new ArrayList<>();
        for (int i = 0; i < 1000; i++)
        {
            stringList.add(String.valueOf(i));
        }

        Iterator<String> iterator = stringList.iterator();
        while (iterator.hasNext())
        {
            String s = iterator.next();
            System.out.println(s);
            stringList.add("1");
        }
    }
}

3.2. fail-safe List

public class ConcurrentModificationExceptionTest2
{
    public static void main(String[] args)
    {
        CopyOnWriteArrayList<String> stringList = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 1000; i++)
        {
            stringList.add(String.valueOf(i));
        }

        Iterator<String> iterator = stringList.iterator();
        while (iterator.hasNext())
        {
            String s = iterator.next();
            System.out.println(s);
            stringList.add("1");//The newly added value will not be printed by the println above.
        }
        System.out.println(stringList);
    }
}

4. Implementation Analysis

4.1. fail-fast

4.1.1. modCount Is Modified When Elements Are Added, Removed, or Changed

  • ArrayList add
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;//modCount is modified here.

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

4.1.2. expectedModCount Is Initialized to modCount When the Iterator Is Created

  • iterator
public Iterator<E> iterator() {
    // Create the iterator.
    return new Itr();
}
  • ArrayList.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;//expectedModCount is initialized to ArrayList's current modCount.

    Itr() {}

4.1.3. Iteration Checks Whether expectedModCount Equals modCount

  • ArrayList.Itr#next
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];
}
  • ArrayList.Itr#checkForComodification
final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

It can be seen that if the list is modified during iteration, when next obtains the next element it checks modCount, which will no longer equal expectedModCount, so a ConcurrentModificationException is thrown.

4.2. fail-safe

4.2.1. Add/Delete/Modify Creates a New Array by Copying the Old Array

  • add
public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // Copy the old array before modifying it.
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

4.2.2. The Iterator Uses the Old Array When It Is Created

  • CopyOnWriteArrayList#iterator
public Iterator<E> iterator() {
    // Create CopyOnWriteArrayList.COWIterator#COWIterator.
    return new COWIterator<E>(getArray(), 0);
}
  • CopyOnWriteArrayList.COWIterator#COWIterator
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;
    }

4.2.3. Iteration Uses the Old Array

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

5. Summary

  • The principle of fail-fast is that when an iterator is created, expectedModCount = modCount is initialized. During iteration, these two values are checked for equality. If the list is modified during iteration, then expectedModCount != modCount, and an exception is thrown.
  • The principle of fail-safe here is that when the iterator is created, it uses a copy of the original data. Iteration works on that copied data, so modifying the list during iteration does not affect the copy, but the iterator cannot see the latest data.

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub