1。フェールフーストの紹介
「Fast Fail」はFail-Fastとも呼ばれ、Javaコレクションのエラー検出メカニズムです。スレッドがコレクションを繰り返している場合、他のスレッドはコレクションを構造的に変更することは許可されていません。
たとえば、2つのスレッド(スレッド1とスレッド2)があり、スレッド1がセットAスルーイテレータの要素を横断するとします。ある時点で、スレッド2は、セットa(設定要素のコンテンツを単に変更するのではなく、構造の変更)の構造を変更すると、プログラムは並行したModificationException例外をスローし、それによりフェイルファストを生成します。
Iteratorの高速障害動作は保証することはできません。エラーが発生することを保証することはできないため、Bugsの検出にのみ同時型変更を使用する必要があります。
java.utilパッケージのすべてのコレクションクラスはすぐに失敗しますが、java.util.concurrentパッケージのコレクションクラスは安全に失敗します。
故障したイテレーターはすぐにconcurrentModificationExceptionをスローしますが、故障するイテレーターはこの例外を安全にスローすることはありません。
2つのフェイルファスト例
サンプルコード:( fastfailtest.java)
Import Java.util。*; Java.util.concurrent。 * *ファーストフェイルイベントの条件:複数のスレッドがコレクションで動作する場合、スレッドのいずれかがコレクションを繰り返して繰り返した場合、コレクションのコンテンツは他のスレッドによって変更されます。 concurrentModificationException例外がスローされます。 *ファーストフェイルソリューション:util.concurrentコレクションパッケージの下の対応するクラスを介して処理すると、ファーストフェイルイベントは生成されません。 * *この例では、ArrayListとCopyonWritearrayListの2つのケースがそれぞれテストされています。 ArrayListはファーストフェイルイベントを生成しますが、CopyOnWritearRayListはファーストフェイルイベントを生成しません。 *(01)ArrayListを使用すると、ファストフェイルイベントが生成され、ConcurrentModificationException例外がスローされます。定義は次のとおりです。 * private static list <string> list = new ArrayList <String>(); *(02)CopyOnWritearRayListを使用する場合、ファーストフェイルイベントは生成されません。定義は次のとおりです。 * private static list <string> list = new copyonwritearraylist <string>(); * * @author skywang */public class fastfailtest {private static list <string> list = new ArrayList <String>(); // private static list <string> list = new CopyOnWritearrayList <String>(); public static void main(string [] args){//同時に2つのスレッドを起動して、リストに操作します! new shoodone()。start(); new shoodtwo()。start(); } private static void printall(){system.out.println( "");文字列値= null; iterator iter = list.iterator(); while(iter.hasnext()){value =(string)iter.next(); System.out.print(value+"、"); }} /*** 0,1,2,3,4,5をリストに追加します。数字を追加した後、printall() */ private static class threadone extends thread {public void run(){int i = 0; while(i <6){list.add(string.valueof(i)); printall(); i ++; }}} /*** 10,11,12,13,14,15をリストに追加します。各番号が追加されると、printall() */ private static class threadtwo拡張スレッド{public void run(){int i = 10; while(i <16){list.add(string.valueof(i)); printall(); i ++; }}}}結果としてコードを実行し、例外をスローしますjava.util.concurrentModificationexception!つまり、フェイルファストイベントが生成されます!
結果の説明
(01)FastFailTestで、2つのスレッドを同時に起動して、new SthreadOne()。start()およびnew SthreadTwo()。start()を介してリストを操作します。
Threadoneスレッド:0、1、2、3、4、5をリストに追加します。各番号が追加された後、リスト全体がprintall()を介して通過します。
threadtwoスレッド:10、11、12、13、14、15をリストに追加します。各番号が追加された後、リスト全体がprintall()を介して通過します。
(02)スレッドがリストを通過すると、リストのコンテンツが別のスレッドによって変更されます。 concurrentmodificationexceptionの例外がスローされ、その結果、フェイルファストイベントが発生します。
3。フェイルファストソリューション
故障メカニズムは、エラー検出メカニズムです。 JDKはフェイルファストメカニズムが発生することを保証しないため、エラーの検出にのみ使用できます。マルチスレッド環境でフェイルファーストメカニズムのコレクションを使用する場合は、「java.util.concurrentパッケージの下のクラス」を使用して、java.utilパッケージの下でクラスを置き換えることをお勧めします。
したがって、この例では、ArrayListをjava.util.concurrentパッケージの下の対応するクラスに置き換える必要があります。つまり、コード
private static list <string> list = new ArrayList <String>();
置き換えます
private static list <string> list = new CopyOnWritearRayList <String>();
このソリューションは解決できます。
4。フェイルファースト原則
Fail-Fastイベントが生成されます。これは、ConcurrentModificiationException例外をスローすることでトリガーされます。
それでは、ArrayListはどのように同時モジオ化Exception例外を投げますか?
ConcurrentModificationExceptionは、操作するときにスローされる例外であることがわかっています。最初にIteratorソースコードを見てみましょう。 ArrayListのイテレーターは、親クラスAbstractList.javaに実装されています。コードは次のとおりです。
パッケージjava.util;
パブリックアブストラクトクラスAbstractList <e>拡張AbstractCollection <e> abstraction list <e> {... // AbstractList //の一意の属性//修正されたリストの数を記録するために使用される(操作操作などを追加/削除)、modcount+1保護されたトランジェントイントルモジュート= 0; //リストのIteratorを返します。実際、ITRオブジェクトを返すことです。 public Iterator <e> iterator(){return new itr(); } // ITRは、Iterator(Iterator)の実装クラスです。プライベートクラスITRはIterator <e> {int cursor = 0; int lastret = -1; //番号のレコード値を変更します。 //新しいiTr()オブジェクトが作成されるたびに、新しいオブジェクトが作成されたときの対応するmodcountが保存されます。 //リスト内の要素を通過するたびに、expectsModCountとmodcountが等しいかどうかを比較します。 //等しくない場合、ConcurrentModificationExceptionの例外がスローされ、その結果、フェイルファストイベントが発生します。 int expectsModCount = modCount; public boolean hasnext(){return cursor!= size(); } public e next(){//次の要素を取得する前に、「新しいITRオブジェクトを作成するときに保存されたmodcount」と「current modcount」が等しいかどうかが判断されます。 //等しくない場合、concurrentModificationExceptionの例外がスローされ、フェイルファストイベントが発生します。 checkforcomodification(); {e next = get(cursor); Lastret = cursor ++;次に戻ります。 } catch(indexoutofboundsexception e){checkforcomodification();新しいnosuchelementexception(); }} public void remove(){if(lastret == -1)throw new IllegalStateException(); checkforcomodification(); try {abstractlist.this.remove(lastret); if(lastret <cursor)cursor-;ラストレット= -1; expectsModCount = modCount; } catch(indexoutofboundsexception e){新しいconcurrentModificationException(); }} final void checkforcomodification(){if(modcount!= expectsModCount)throw new concurrentModificiationException(); }} ...}このことから、checkforcomodification()がnext()とremove()が呼び出されたときに実行されることがわかります。 「modcountがexpectsModCountと等しくない場合」の場合、concurrentModificationExceptionの例外がスローされているため、フェイルファストイベントが生じます。
フェイルファーストメカニズムを理解するには、「ModCountが期待されるModCountと等しくない」を理解する必要があります!
ITRクラスから、ITRオブジェクトを作成する際にModCountに期待されるModCountが割り当てられていることがわかります。 ITRを通じて、expectsModCountを等しくないように変更できないことがわかっています。したがって、検証する必要があるのは、ModCountが変更される場合です。
次に、ArrayListのソースコードを確認して、ModCountの変更方法を確認しましょう。
パッケージjava.util; public class arraylist <e> extends abstractlist <e> abstractlist <e>を実装<e>、ランダムアクセス、クローン可能、java.io.serializable {... //リスト内の容量が変更されると、対応する同期関数のパブリックボイドensurecaity(int mincapacity){modcount++++; int oldcapacity = elementdata.length; if(mincapacity> oldcapacity){Object OldData [] = elementData; int newcapacity =(oldcapacity * 3)/2 + 1; if(newcapacity <mincapacity)newcapacity = mincapacity; // mincapacityは通常サイズに近いため、これはwinです。 }} // queueの最後のパブリックブールアディング(e e){// modcount ensurecapacity(size + 1); // increments modcount !! elementData [size ++] = e; 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;サイズ++; } //コレクションを追加するパブリックブーリアンaddall(collection <?extends e> c){object [] a = c.toarray(); int numnew = a.length; // modcount ensurecapacity(size + numnew)を変更する; // modCount System.ArrayCopy(a、0、elementData、size、numnew); size += numnew; 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; // GCにその仕事をさせてください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; // GCにその仕事をさせます} //コレクションをクリアしますpublic void clear(){// modcount modcount ++を変更します。 // GCに(int i = 0; i <size; i ++)elementData [i] = nullの仕事をさせます。サイズ= 0; } ...}このことから、add()、remove()、またはclear()であろうと、セット内の要素の数を変更する限り、modcountの値が変更されることがわかりました。
次に、フェイルファストの生成方法を体系的に整理しましょう。手順は次のとおりです。
(01)ArrayListとして新しいArrayList名を作成します。
(02)ArrayListにコンテンツを追加します。
(03)新しい「スレッドA」を作成し、「スレッドA」のイテレータを介してアレイリストの値を繰り返し読み取ります。
(04)新しい「スレッドB」を作成し、「スレッドB」のアレイリストで「ノードA」を削除します。
(05)現時点では、興味深いイベントが発生します。
ある時点で、「スレッドA」はアレイリストのイテレーターを作成します。この時点で、「ノードA」はまだアレイリストに存在します。 ArrayListを作成する場合、ModCount = ModCount(現時点では値がNであると仮定)。
ArrayListを通過するプロセス中のある時点で、「スレッドB」が実行され、「スレッドB」がアレイリストで「ノード」を削除します。 「スレッドB」が削除操作のためにremove()を実行すると、「modcount ++」がremove()で実行され、modcountはn+1になります。
「スレッドA」は横断します。次の()関数を実行すると、checkforcomodification()が呼び出され、「expectsmodcount」と「modcount」のサイズを比較します。 「spedctionmodcount = n」、 "modcount = n+1"であるため、concurrentModificationExceptionの例外がスローされ、フェールファストイベントが発生します。
この時点で、フェイルファストがどのように生成されるかを完全に理解しています!
つまり、複数のスレッドが同じセットで動作する場合、スレッドがセットにアクセスすると、セットのコンテンツが他のスレッドによって変更されます(つまり、他のスレッドは、add、削除、クリア、その他のメソッドを介してmodcountの値を変更します)。現時点では、同時モジェンスエクセプトの例外がスローされ、その結果、フェイルファストイベントが発生します。
5。フェールファーストを解決する原則
上記は、「故障メカニズムを解決する方法」を説明し、「故障の根本原因」も知っています。次に、java.util.concurrentパッケージでフェイルファストイベントを解決する方法についてさらに説明しましょう。
ArrayListに対応するCopyOnWriteArrayListで説明しましょう。まず、copyonwritearraylistのソースコードを見てみましょう。
パッケージjava.util.concurrent; Import java.util。*; import java.util.concurrent.locks。*; import sun.misc.unsafe; public copyonwritearraylist <e> impolt <e>、emplements list <e>、randomacess、cloneable、java.io.io. iterator(){新しいコレクションクラスのファーストフェイル実装メソッドを返すことはほぼ同じです。最も簡単なアレイリストを例として見てみましょう。保護された一時的なint modcount = 0; ArrayListを変更する回数を記録します。たとえば、add()、remove()などを呼び出すと、データを変更すると、modcount ++が変更されます。保護された一時的なint modcount = 0; ArrayListを変更する回数を記録します。たとえば、add()、remove()などを呼び出すと、データを変更すると、modcount ++が変更されます。 Cowiterator <e>(getArray()、0); } ... private static class cowiterator <e> Implements listiterator <e> {private final object [] snapshot;プライベートINTカーソル; private Cowiterator(Object [] Elements、int initialCursor){cursor = initialCursor; //新しいCowiteratorを作成するときは、コレクションの要素を新しいコピー配列に保存します。 //このようにして、元のセットのデータが変更されると、コピーデータの値も変更されません。 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 fortion(){if(!hasprevious())throw new nosuchelementexception(); return(e)snapshot [ - カーソル]; } public int nextindex(){return cursor; } public int fortion index(){return cursor-1; } public void remove(){新しいunsupportedoperationexception(); } public void set(e e){supl unsupportedoperationexception(); } public void add(e e){supportedoperationexception(); }} ...}これから私たちは見ることができます:
(01)ArrayListがAbstractListから継承するのとは異なり、CopyOnWritearRayListはAbstractListから継承せず、リストインターフェイスのみを実装します。
(02)ArrayListのIterator()関数によって返されるIteratorは、AbstractListで実装されます。 CopyonWritearrayListは、イテレーター自体を実装しています。
(03)ArrayListのIterator実装クラスでNext()が呼び出された場合、「CheckForComodification()を呼び出して、「HecostsModCount」と「ModCount」のサイズを比較します。ただし、CopyOnWritearrayListのIterator実装クラスには、いわゆるCheckForcomodification()はありません。
6。概要
Hashmap(ArrayList)はスレッドセーフではないため、他のスレッドがイテレーターを使用するプロセス中にマップを変更する場合(ここでの変更は、単にコレクションコンテンツの要素を変更するためではなく、構造的変更を指します)、同時に故障戦略を介して故障戦略を介して実装されます。 ModCountは変更の数です。この値は、HashMap(ArrayList)コンテンツの変更に追加されます。次に、イテレーターの初期化中に、この値はIteratorのHecdestModCountに割り当てられます。
しかし、フェイルファーストの動作は保証されていないため、この例外に依存する慣行は間違っています