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(); } }
protected transient int modCount = 0;
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 /** 省略此处代码 */ }
3. フェイルファスト ソリューション
これまでの例とソース コード分析を通じて、フェイルファストのメカニズムを基本的に理解できたと思います。理由と解決策。ここには 2 つの解決策があります。 解決策 1: トラバーサル プロセス中に、modCount 値の変更を伴うすべての場所に synchronized を追加するか、Collections.synchronizedList を直接使用すると、問題を解決できます。ただし、追加や削除によって発生する同期ロックによってトラバーサル操作がブロックされる可能性があるため、これはお勧めできません。 オプション 2: CopyOnWriteArrayList を使用して ArrayList を置き換えます。この解決策をお勧めします。以上がフェイルファストメカニズムの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。