java -- collection error mechanism -- fail-fast

java -- collection error mechanism -- fail-fast

 

 

      The fail-fast mechanism is an error mechanism in the Java Collection. A fail-fast event may occur when multiple threads operate on the contents of the same collection.

 

      For example: when a thread A traverses a collection through an iterator, if the content of the collection is changed by other threads; then when thread A accesses the collection, a ConcurrentModificationException will be thrown and a fail-fast event will be generated.

 

 

 

fail-fast example

 

1. In FastFailTest, two threads are started at the same time to operate the list through new ThreadOne().start() and new ThreadTwo().start().

 

  • ThreadOne thread: Add 0,1,2,3,4,5 to the list in turn. After each number is added, the entire list is traversed through printAll().
  • ThreadTwo thread: Add 10, 11, 12, 13, 14, 15 to the list in sequence. After each number is added, the entire list is traversed through printAll().

2. When a thread traverses the list, the content of the list is changed by another thread; ConcurrentModificationException will be thrown, resulting in a fail-fast event.

 

 

 

working principle

 

  1. There are two global variables to record whether the collection has changed
  2. When calling next() and remove(), both will compare whether modCount is equal to expectedModCount, and if not, throw ConcurrentModificationException.
  3. Whether it is add(), remove(), or clear(), as long as it involves modifying the number of elements in the collection, it will change the value of modCount.
  4. When thread A creates the arrayList, expectedModCount = modCount (assuming their value is N at this time).
  5. Then another thread changes the line add(), remove(), or clear() of the arrayList, and it will modCount++.
  6. If thread A is traversing the arrayList, thread B modifies the data in the collection, and then compares modCount! = expectedModCount, an exception will be thrown.

 

 

fail-fast solution

 

      The fail-fast mechanism is an error detection mechanism. It can only be used to detect errors, because the JDK does not guarantee that the fail-fast mechanism will happen. If you use a collection of fail-fast mechanisms in a multi-threaded environment, it is recommended to use "classes under the java.util.concurrent package" instead of "classes under the java.util package".

 

Therefore, in this example, you only need to replace the ArrayList with the corresponding class in the java.util.concurrent package.

That is, put the code

private static List<String> list = new ArrayList<String>();

replace with

private static List<String> list = new CopyOnWriteArrayList<String>();

 

 

 

The principle of solving fail-fast

 

      In the above, the "measures to solve the fail-fast mechanism" are explained, and the "root cause of fail-fast" is also known. Next, let's talk more about how the fail-fast event is resolved in the java.util.concurrent package.

 

It is still explained with CopyOnWriteArrayList corresponding to ArrayList. Let's first look at the source code of CopyOnWriteArrayList:

package java.util.concurrent;
import java.util. *;
import java.util.concurrent.locks.*;
import sun.misc.Unsafe;

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    ...

    // return the iterator corresponding to the collection
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

    ...
   
    private static class COWIterator<E> implements ListIterator<E> {
        private final Object[] snapshot;

        private int cursor;

        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            // When creating a new COWIterator, save the elements in the collection to a new copy array.
            // In this way, when the data of the original collection changes, the value in the copied data will not change.
            snapshot = elements;
        }

        public boolean hasNext() {
            return cursor < snapshot.length;
        }

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

}

 From this, we can see that:

 

1. Unlike ArrayList, which inherits from AbstractList, CopyOnWriteArrayList does not inherit from AbstractList, it just implements the List interface.

 

2. The Iterator returned by the iterator() function of ArrayList is implemented in AbstractList; while CopyOnWriteArrayList implements Iterator by itself.

 

3. When next() is called in the Iterator implementation class of ArrayList, it will "call checkForComodification() to compare the size of 'expectedModCount' and 'modCount'"; however, in the Iterator implementation class of CopyOnWriteArrayList, there is no so-called checkForCommodification(), let alone ConcurrentModificationException will be thrown! 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327057256&siteId=291194637