ホームページ  >  記事  >  Java  >  フェイルファストメカニズム

フェイルファストメカニズム

(*-*)浩
(*-*)浩転載
2019-09-02 16:39:451876ブラウズ

JDK コレクションでは、次のような単語がよく見られます:

フェイルファストメカニズム

たとえば、ArrayList:

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽
最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭
代器的快速失败行为应该仅用于检测 bug。

HashMap:

注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大
努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应
该仅用于检测程序错误。

これら 2 つの段落で「フェイルファスト」について繰り返し言及しています。では、「フェイルファスト」メカニズムとは何でしょうか?

「高速障害」はフェイルファストであり、Java コレクションのエラー検出メカニズムです。複数のスレッドがコレクションの構造変更を実行すると、フェイルファスト メカニズムが発生する可能性があります。可能性はあるが、確実ではないことを覚えておいてください。例: 2 つのスレッドがあるとします (スレッド 1、スレッド 2)。スレッド 1 は反復子を介してセット A の要素をトラバースしています。ある時点で、スレッド 2 はセット A の構造を変更します (これは構造の変更であり、変更ではありません)。コレクション要素の内容を単純に変更する場合)、プログラムはこの時点で ConcurrentModificationException 例外をスローし、フェイルファスト メカニズムが発生します。

#1. フェイルファストの例

public class FailFastTest {
    private static List<Integer> list = new ArrayList<>();
 
    /**
     * @desc:线程one迭代list
     * @Project:test
     * @file:FailFastTest.java
     * @Authro:chenssy
     * @data:2014年7月26日
     */
    private static class threadOne extends Thread{
        public void run() {
            Iterator<Integer> iterator = list.iterator();
            while(iterator.hasNext()){
                int i = iterator.next();
                System.out.println("ThreadOne 遍历:" + i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    /**
     * @desc:当i == 3时,修改list
     * @Project:test
     * @file:FailFastTest.java
     * @Authro:chenssy
     * @data:2014年7月26日
     */
    private static class threadTwo extends Thread{
        public void run(){
            int i = 0 ;
            while(i < 6){
                System.out.println("ThreadTwo run:" + i);
                if(i == 3){
                    list.remove(i);
                }
                i++;
            }
        }
    }
 
    public static void main(String[] args) {
        for(int i = 0 ; i < 10;i++){
            list.add(i);
        }
        new threadOne().start();
        new threadTwo().start();
    }
}

実行結果:

ThreadOne 遍历:0
ThreadTwo run:0
ThreadTwo run:1
ThreadTwo run:2
ThreadTwo run:3
ThreadTwo run:4
ThreadTwo run:5
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at test.ArrayListTest$threadOne.run(ArrayListTest.java:23

2. フェイルファストの原因

上記の例と説明を通じて、フェイルファストの理由は、プログラムがコレクションを反復するときに、スレッドがコレクションに構造的な変更を加えるためであることが最初にわかりました。このとき、反復子の ConcurrentModificationException 例外情報は、スローされ、フェイルファストが発生します。

フェイルファスト メカニズムを理解するには、まず ConcurrentModificationException 例外を理解する必要があります。この例外は、メソッドがオブジェクトの同時変更を検出したが、そのような変更は許可しない場合にスローされます。同時に、この例外は、オブジェクトが異なるスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。単一のスレッドがルールに違反した場合も、例外がスローされる可能性があります。

確かにイテレータのフェイルファスト動作は保証できず、このエラーが発生することも保証されませんが、フェイルファスト操作は ConcurrentModificationException 例外をスローするために最善を尽くします。このような操作の精度を向上させるために、この例外に依存するプログラムを作成するのは間違いであり、正しいアプローチは次のとおりです: ConcurrentModificationException はバグを検出するためにのみ使用する必要があります。以下では、ArrayList を例として使用して、フェイルファストの理由をさらに分析します。

イテレータを操作するときにフェイルファストが発生することは前からわかっています。次に、ArrayList のイテレータのソース コードを見てみましょう。

private class Itr implements Iterator<E> {
        int cursor;
        int lastRet = -1;
        int expectedModCount = ArrayList.this.modCount;
 
        public boolean hasNext() {
            return (this.cursor != ArrayList.this.size);
        }
 
        public E next() {
            checkForComodification();
            /** 省略此处代码 */
        }
 
        public void remove() {
            if (this.lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            /** 省略此处代码 */
        }
 
        final void checkForComodification() {
            if (ArrayList.this.modCount == this.expectedModCount)
                return;
            throw new ConcurrentModificationException();
        }
    }

上記のソース コードから、next() および Remove() を呼び出すときにイテレータが常に checkForComodification() メソッドを呼び出すことがわかります。このメソッドは主に modCount == ExpectedModCount? を検出するためのもので、そうでない場合は ConcurrentModificationException 例外がスローされ、フェイルファスト メカニズムが生成されます。したがって、フェイルファスト メカニズムが発生する理由を理解するには、modCount != ExpectedModCount の理由と、それらの値がいつ変更されたかを理解する必要があります。

expectedModCount は Itr で定義されています: int ExpectedModCount = ArrayList.this.modCount; したがって、その値は変更できないため、変更されるのは modCount です。 modCount は AbstractList で定義され、グローバル変数です:

 protected transient int modCount = 0;

それでは、いつ、どのような理由で変更されるのでしょうか? ArrayList のソース コードを見てください:

public boolean add(E paramE) {
    ensureCapacityInternal(this.size + 1);
    /** 省略此处代码 */
}

private void ensureCapacityInternal(int paramInt) {
    if (this.elementData == EMPTY_ELEMENTDATA)
        paramInt = Math.max(10, paramInt);
    ensureExplicitCapacity(paramInt);
}

private void ensureExplicitCapacity(int paramInt) {
    this.modCount += 1;    //修改modCount
    /** 省略此处代码 */
}

public boolean remove(Object paramObject) {
    int i;
    if (paramObject == null)
        for (i = 0; i < this.size; ++i) {
            if (this.elementData[i] != null)
                continue;
            fastRemove(i);
            return true;
        }
    else
        for (i = 0; i < this.size; ++i) {
            if (!(paramObject.equals(this.elementData[i])))
                continue;
            fastRemove(i);
            return true;
        }
    return false;
}

private void fastRemove(int paramInt) {
    this.modCount += 1;   //修改modCount
    /** 省略此处代码 */
}

public void clear() {
    this.modCount += 1;    //修改modCount
    /** 省略此处代码 */
}

上記のソース コードから、ArrayList 要素の数の変更が含まれる限り、ArrayList の add、remove、clear メソッドに関係なく、 , modCountが変化します。したがって、ここでは、expectedModCount 値と modCount の変化が同期していないため、両者の間に不等号が生じ、フェイルファスト メカニズムが発生すると事前に判断できます。フェイルファストの根本原因がわかれば、次のようなシナリオが考えられます。

2 つのスレッド (スレッド A、スレッド B) があり、そのうちのスレッド A はリストの走査を担当し、スレッド B はリストを変更します。 。スレッド A がリストを走査しているとき (この時点では ExpectModCount = modCount=N)、スレッドが開始され、同時にスレッド B が要素を追加します。これは、modCount の値が変化することを意味します (modCount 1 = N 1)。 。スレッド A が引き続きトラバースして次のメソッドを実行すると、checkForComodification メソッドに、expectedModCount = N と modCount = N 1 が等しくないことが通知され、このとき、ConcurrentModificationException 例外がスローされ、失敗します。速いメカニズム。

ということで、ここまででフェイルファストの根本原因を完全に理解しました。原因がわかれば、解決策も見つけやすくなります。

3. フェイルファスト ソリューション

これまでの例とソース コード分析を通じて、フェイルファストのメカニズムを基本的に理解できたと思います。理由と解決策。ここには 2 つの解決策があります。

解決策 1: トラバーサル プロセス中に、modCount 値の変更を伴うすべての場所に synchronized を追加するか、Collections.synchronizedList を直接使用すると、問題を解決できます。ただし、追加や削除によって発生する同期ロックによってトラバーサル操作がブロックされる可能性があるため、これはお勧めできません。

オプション 2: CopyOnWriteArrayList を使用して ArrayList を置き換えます。この解決策をお勧めします。

以上がフェイルファストメカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。