私はCollection
(JPAによって間接的に使用されるHashMap
を使用していますが、そうなります)を使用していますが、明らかにランダムにコードがConcurrentModificationException
をスローします。何が原因で、この問題を修正するにはどうすればよいですか?何らかの同期を使用することにより、おそらく?
完全なスタックトレースは次のとおりです。
Exception in thread "pool-1-thread-1" Java.util.ConcurrentModificationException
at Java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at Java.util.HashMap$ValueIterator.next(Unknown Source)
at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.Java:555)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.Java:296)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.Java:242)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.Java:219)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.Java:169)
at org.hibernate.engine.Cascade.cascade(Cascade.Java:130)
これは同期の問題ではありません。これは、反復される基になるコレクションがIterator以外の何かによって変更された場合に発生します。
Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
Entry item = it.next();
map.remove(item.getKey());
}
2回目にit.hasNext()が呼び出されると、ConcurrentModificationExceptionがスローされます。
正しいアプローチは
Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
Entry item = it.next();
it.remove();
}
このイテレータがremove()操作をサポートすると仮定します。
プレーンなHashMapの代わりにConcurrentHashMapを使用してみてください
Collection
を変更し、 Collection
を使用してIterator
を繰り返し処理する場合は許可されませんほとんどのCollection
クラス。 Javaライブラリーは、Collection
を変更する試みを呼び出しますが、残念ながら複数のスレッドによる同時変更が唯一の原因である可能性がありますが、そうではありません。スレッドを1つだけ使用すると、Collection
の反復子を作成できます( Collection.iterator()
、または enhanced for
loop )、反復を開始し( Iterator.next()
を使用、または同等の拡張for
ループの本体を入力)、Collection
を変更して、反復を続行します。
プログラマを支援するために、someそれらのCollection
クラスの実装attempt誤った同時変更を検出し、検出した場合はConcurrentModificationException
をスローします。ただし、すべての同時変更の検出を保証することは一般的に不可能であり、実用的ではありません。したがって、Collection
を誤って使用しても、ConcurrentModificationException
がスローされるとは限りません。
ConcurrentModificationException
のドキュメントには次のように書かれています。
この例外は、そのような変更が許可されていないときにオブジェクトの同時変更を検出したメソッドによってスローされる場合があります...
この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。単一のスレッドがオブジェクトのコントラクトに違反するメソッド呼び出しのシーケンスを発行すると、オブジェクトはこの例外をスローする可能性があります...
一般に、非同期の同時変更が存在する場合、ハードな保証を行うことは不可能であるため、フェイルファースト動作は保証できないことに注意してください。フェイルファースト操作は、ベストエフォートベースで
ConcurrentModificationException
をスローします。
ご了承ください
HashSet
、 HashMap
、 TreeSet
のドキュメント/および ArrayList
クラスはこれを言っています:
[このクラスから直接または間接的に]返される反復子はフェイルファーストです。反復子の作成後、反復子自身のremoveメソッド以外の方法で[コレクション]が変更されると、
Iterator
はConcurrentModificationException
をスローします。したがって、同時変更に直面して、イテレータは、将来の未定の時点でarbitrary意的で非決定的な動作を危険にさらすのではなく、迅速かつクリーンに失敗します。イテレータのフェイルファースト動作は、一般的に言えば、非同期の同時変更が存在する場合にハードな保証を行うことができないため、保証できないことに注意してください。フェイルファストイテレータは、ベストエフォートベースで
ConcurrentModificationException
をスローします。したがって、その正確性についてこの例外に依存するプログラムを作成するのは誤りです。イテレータのフェイルファースト動作は、バグを検出するためにのみ使用する必要があります。
ふるまいは「保証することはできません」であり、「ベストエフォートベースでのみ」であることに注意してください。
Map
インターフェースのいくつかのメソッドのドキュメントは次のように言っています:
非並行実装はこのメソッドをオーバーライドし、マッピング関数が計算中にこのマップを変更することが検出された場合、ベストエフォートベースで
ConcurrentModificationException
をスローする必要があります。並行実装では、このメソッドをオーバーライドし、マッピング関数が計算中にこのマップを変更し、結果として計算が完了しないことが検出された場合、ベストエフォートベースでIllegalStateException
をスローする必要があります。
ここでも、検出には「ベストエフォートベース」のみが必要であり、ConcurrentModificationException
は非並行(スレッドセーフでない)クラスに対してのみ明示的に推奨されることに注意してください。
ConcurrentModificationException
のデバッグそのため、ConcurrentModificationException
によるスタックトレースが表示された場合、その原因がCollection
への安全でないマルチスレッドアクセスであるとすぐに推測することはできません。 stack-trace を調べて、Collection
のどのクラスが例外をスローしたか(クラスのメソッドが直接または間接的にそれをスローします)、およびどのCollection
オブジェクトに対して。次に、そのオブジェクトを変更できる場所を調べる必要があります。
Collection
に対する拡張for
ループ内のCollection
の変更です。ソースコードにIterator
オブジェクトが表示されないからといって、そこにIterator
がないことを意味しません!幸いなことに、障害のあるfor
ループのステートメントの1つは通常スタックトレースにあるため、エラーの追跡は通常簡単です。Collection
オブジェクトへの参照を渡す場合です。 変更不可コレクションのビュー( Collections.unmodifiableList()
によって生成されるなど)は、変更可能なコレクションへの参照を保持するため、 「変更不可能な」コレクションに対する反復は例外をスローする可能性があります (変更は他の場所で行われています)。その他viewsCollection
の subリスト など、 Map
エントリセットMap
キーセット は、元の(変更可能な)Collection
への参照も保持します。これは、 Collection
などのスレッドセーフなCopyOnWriteList
でも問題になる可能性があります。スレッドセーフ(同時)collectinsが例外をスローすることは決してないと想定しないでください。Collection
を変更できるかは、場合によっては予期しないことです。たとえば、 LinkedHashMap.get()
はそのコレクションを変更します 。可能な場合は、Collection
オブジェクトへのすべての参照を制限することで、同時変更を防止しやすくなります。 Collection
をprivate
オブジェクトまたはローカル変数にし、メソッドからCollection
またはその反復子への参照を返さないでください。その場合、allCollection
を変更できる場所を調べる方がはるかに簡単です。 Collection
を複数のスレッドで使用する場合は、適切な同期とロックを使用してのみスレッドがCollection
にアクセスするようにすることが実際的です。
Java同期の問題ではなく、データベースのロックの問題に似ています。
すべての永続クラスにバージョンを追加して整理できるかどうかはわかりませんが、Hibernateがテーブル内の行への排他的アクセスを提供できる方法の1つです。
分離レベルを高くする必要がある可能性があります。 「ダーティリード」を許可する場合は、おそらくシリアライズ可能にする必要があります。
私のようにマップを繰り返しながらマップからいくつかのエントリを削除しようとしている場合、選択した回答は、変更前にコンテキストに直接適用できないことに注意してください。
ここでは、初心者が時間を節約できるように、実際の例を示します。
HashMap<Character,Integer> map=new HashMap();
//adding some entries to the map
...
int threshold;
//initialize the threshold
...
Iterator it=map.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Character,Integer> item=(Map.Entry<Character,Integer>)it.next();
//it.remove() will delete the item from the map
if((Integer)item.getValue()<threshold){
it.remove();
}
何をしようとしているかに応じて、CopyOnWriteArrayListまたはCopyOnWriteArraySetを試してください。