Java~The fail-fast mechanism in the collection

Introduction

We know that many collections under the Collection interface in Java are not thread-safe. For example, java.util.ArrayList is not thread-safe, so if other threads modify the list while using the iterator, ConcurrentModificationException will be thrown. This is the so-called fail-fast strategy.

The implementation of this strategy in the source code is through the modCount field. ModCount is the number of modifications as the name suggests. Modifications to the contents of the ArrayList will increase this value. Then this value will be assigned to the expectedModCount of the iterator during the iterator initialization process. In the iterative process, judge whether modCount and expectedModCount are equal. If they are not equal, it means that other threads have modified the list.
Note that modCount is declared as volatile to ensure the visibility of changes between threads.

modCount和expectedModCount

modCount and expectedModCount are used to represent the number of modifications, where modCount represents the number of modifications of the collection, which includes the modifications made when the add, remove, and clear methods of the collection itself are called, and the modification methods of the collection iterator are called. modify. The expectedModCount is the number of times the iterator modifies the collection.

The purpose of setting expectedModCount is to ensure that during the use of the iterator, the list object can only have this iterator to modify the list.

When creating the iterator, the value of the modCount of the object is passed to the expectedModCount of the iterator:

  private class ListItr implements ListIterator<E> {
    
    
         private Node<E> lastReturned;
         private Node<E> next;
         private int nextIndex;
         private int expectedModCount = modCount;

If you create multiple iterators to modify a collection object, then there will be one modCount and multiple expectedModCount, and the value of modCount will be different, which leads to the inconsistency of the values ​​of moCount and expectedModCount, resulting in an exception :

public E next() {
    
    
            checkForComodification();
            if (!hasNext())
                throw new NoSuchElementException();

            lastReturned = next;
            next = next.next;
            nextIndex++;
            return lastReturned.item;
        }

The checkForComodification in the code above will check whether the values ​​of modCount and expectedModCount are the same, and throw an exception if they do not match.

   final void checkForComodification() {
    
    
             if (modCount != expectedModCount)
                 throw new ConcurrentModificationException();
         

How modCount is modified

    // 添加元素到队列最后
    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<? extends E> 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;
    }
  • That is to say, modcount++ will be executed when data is added or deleted to the collection, so if a thread is still using the iterator to traverse the list, an exception will be found, and a fail-fast will occur.

Comparison of fail-fast and fail-safe

The rapid failure of Iterator is based on the fact that the copy of the underlying collection is a shallow copy, so it is affected by the modification on the source collection. All the collection classes under the java.util package fail fast

All the classes under the java.util.concurrent package use locks to achieve safety failures.

Iterators that fail fast will throw ConcurrentModificationException, while iterators that fail safely will never throw such an exception.

What problem does fail-fast solve

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. It just tells the client that a multi-thread safety problem has occurred in a multi-threaded environment.
Therefore, if the set of fail-fast mechanisms is used in a multithreaded environment, it is recommended to use "classes under the java.util.concurrent package" to replace "classes under the java.util package".

How to resolve fail-fast events

The CopyOnWriteArrayList corresponding to ArrayList is explained. Let's take a look at the source code of CopyOnWriteArrayList:

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

    ...

    // 返回集合对应的迭代器
    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;
            // 新建COWIterator时,将集合中的元素保存到一个新的拷贝数组中。
            // 这样,当原始集合的数据改变,拷贝数据中的值也不会变化。
            snapshot = elements;
        }

        public boolean hasNext() {
    
    
            return cursor < snapshot.length;
        }
  • CopyOnWriteArrayList implements Iterator by itself, and there is no so-called checkForComodification() in the Iterator implementation class of CopyOnWriteArrayList, and it will not throw ConcurrentModificationException.
  • CopyOnWriteArrayList saves the elements in the collection to a new copy array when creating a new COWIterator. In this way, when the data in the original set changes, the value in the copied data will not change.

Guess you like

Origin blog.csdn.net/Shangxingya/article/details/113779220