Java〜コレクション内のフェイルファストメカニズム

前書き

JavaのCollectionインターフェースの下にある多くのコレクションはスレッドセーフではないことがわかっています。たとえば、java.util.ArrayListはスレッドセーフではないため、イテレーターの使用中に他のスレッドがリストを変更すると、ConcurrentModificationExceptionがスローされます。いわゆるフェイルファスト戦略。

ソースコードでのこの戦略の実装は、modCountフィールドを介して行われます。名前が示すように、modCountは変更の数です。ArrayListの内容を変更すると、この値が増加します。次に、この値は、のexpectedModCountに割り当てられます。イテレータ初期化プロセス中のイテレータ。反復プロセスで、modCountとexpectedModCountが等しいかどうかを判断します。等しくない場合は、他のスレッドがリストを変更したことを意味します。
modCountは、スレッド間の変更の可視性を確保するために揮発性として宣言されていることに注意してください。

modCount和expectedModCount

modCountおよびexpectedModCountは、変更の数を表すために使用されます。ここで、modCountは、コレクション自体のadd、remove、およびclearメソッドが呼び出されたときに行われた変更、およびの変更メソッドを含む、コレクションへの変更の数を表します。コレクションイテレータが呼び出されます。変更します。expectedModCountは、イテレーターがコレクションを変更する回数です。

expectedModCountを設定する目的は、イテレーターの使用中に、リストオブジェクトがリストを変更するためにこのイテレーターのみを持つことができるようにすることです。

イテレータを作成すると、オブジェクトのmodCountの値がイテレータのexpectedModCountに渡されます。

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

コレクションオブジェクトを変更するために複数のイテレータを作成すると、1つのmodCountと複数のexpectedModCountが存在し、modCountの値が異なるため、moCountとexpectedModCountの値に不整合が生じ、例外が発生します:

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

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

上記のコードのcheckForComodificationは、modCountとexpectedModCountの値が同じであるかどうかを確認し、一致しない場合は例外をスローします。

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

modCountの変更方法

    // 添加元素到队列最后
    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;
    }
  • つまり、modcount ++は、データがコレクションに追加または削除されたときに実行されるため、スレッドがまだイテレーターを使用してリストをトラバースしている場合、例外が検出され、フェイルファストが発生します。

フェイルファストとフェイルセーフの比較

Iteratorの急速な失敗は、基になるコレクションのコピーが浅いコピーであるという事実に基づいているため、ソースコレクションの変更の影響を受けます。java.utilパッケージのすべてのコレクションクラスはすぐに失敗します

java.util.concurrentパッケージのすべてのクラスは、ロックを使用して安全性の失敗を実現します。

高速で失敗するイテレータはConcurrentModificationExceptionをスローしますが、安全に失敗するイテレータはそのような例外をスローしません。

フェイルファストはどのような問題を解決しますか

フェイルファストメカニズムは、エラー検出メカニズムです。
JDKはフェイルファストメカニズムが発生することを保証しないため、エラーの検出にのみ使用できます。マルチスレッド環境でマルチスレッドセーフの問題が発生したことをクライアントに通知するだけです。
したがって、フェイルファストメカニズムのセットがマルチスレッド環境で使用される場合は、「java.util.concurrentパッケージの下のクラス」を使用して「java.utilパッケージの下のクラス」を置き換えることをお勧めします。

フェイルファストイベントを解決する方法

ArrayListに対応するCopyOnWriteArrayListについて説明します。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はそれ自体でIteratorを実装し、CopyOnWriteArrayListのIterator実装クラスにはいわゆるcheckForComodification()はなく、ConcurrentModificationExceptionをスローしません。
  • CopyOnWriteArrayListは、新しいCOWIteratorを作成するときに、コレクション内の要素を新しいコピー配列に保存します。このように、元のセットのデータが変更されても、コピーされたデータの値は変更されません。

おすすめ

転載: blog.csdn.net/Shangxingya/article/details/113779220