web-dev-qa-db-ja.com

ConcurrentModificationExceptionがスローされる理由とデバッグ方法

私は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)
112
mainstringargs

これは同期の問題ではありません。これは、反復される基になるコレクションが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()操作をサポートすると仮定します。

243
Robin

プレーンなHashMapの代わりにConcurrentHashMapを使用してみてください

60
Chochos

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をスローします。

ご了承ください

  • 例外がスローされる場合がありますスローされますが、がスローされる必要はありません
  • 別のスレッドは必要ありません
  • 例外のスローは保証できません
  • 例外のスローはベストエフォートベースです
  • 例外のスローは 同時変更が検出の場合、caused の場合ではありません

HashSetHashMapTreeSet のドキュメント/および ArrayList クラスはこれを言っています:

[このクラスから直接または間接的に]返される反復子はフェイルファーストです。反復子の作成後、反復子自身のremoveメソッド以外の方法で[コレクション]が変更されると、IteratorConcurrentModificationExceptionをスローします。したがって、同時変更に直面して、イテレータは、将来の未定の時点でarbitrary意的で非決定的な動作を危険にさらすのではなく、迅速かつクリーンに失敗します。

イテレータのフェイルファースト動作は、一般的に言えば、非同期の同時変更が存在する場合にハードな保証を行うことができないため、保証できないことに注意してください。フェイルファストイテレータは、ベストエフォートベースでConcurrentModificationExceptionをスローします。したがって、その正確性についてこの例外に依存するプログラムを作成するのは誤りです。イテレータのフェイルファースト動作は、バグを検出するためにのみ使用する必要があります

ふるまいは「保証することはできません」であり、「ベストエフォートベースでのみ」であることに注意してください。

Map インターフェースのいくつかのメソッドのドキュメントは次のように言っています:

非並行実装はこのメソッドをオーバーライドし、マッピング関数が計算中にこのマップを変更することが検出された場合、ベストエフォートベースでConcurrentModificationExceptionをスローする必要があります。並行実装では、このメソッドをオーバーライドし、マッピング関数が計算中にこのマップを変更し、結果として計算が完了しないことが検出された場合、ベストエフォートベースでIllegalStateExceptionをスローする必要があります。

ここでも、検出には「ベストエフォートベース」のみが必要であり、ConcurrentModificationExceptionは非並行(スレッドセーフでない)クラスに対してのみ明示的に推奨されることに注意してください。

ConcurrentModificationExceptionのデバッグ

そのため、ConcurrentModificationExceptionによるスタックトレースが表示された場合、その原因がCollectionへの安全でないマルチスレッドアクセスであるとすぐに推測することはできません。 stack-trace を調べて、Collectionのどのクラスが例外をスローしたか(クラスのメソッドが直接または間接的にそれをスローします)、およびどのCollectionオブジェクトに対して。次に、そのオブジェクトを変更できる場所を調べる必要があります。

  • 最も一般的な原因は、Collectionに対する拡張forループ内のCollectionの変更です。ソースコードにIteratorオブジェクトが表示されないからといって、そこにIteratorがないことを意味しません!幸いなことに、障害のあるforループのステートメントの1つは通常スタックトレースにあるため、エラーの追跡は通常簡単です。
  • 厄介なケースは、コードがCollectionオブジェクトへの参照を渡す場合です。 変更不可コレクションのビュー( Collections.unmodifiableList() によって生成されるなど)は、変更可能なコレクションへの参照を保持するため、 「変更不可能な」コレクションに対する反復は例外をスローする可能性があります (変更は他の場所で行われています)。その他viewsCollectionsubリスト など、 MapエントリセットMapキーセット は、元の(変更可能な)Collectionへの参照も保持します。これは、 Collection などのスレッドセーフなCopyOnWriteListでも問題になる可能性があります。スレッドセーフ(同時)collectinsが例外をスローすることは決してないと想定しないでください。
  • どの変数がCollectionを変更できるかは、場合によっては予期しないことです。たとえば、 LinkedHashMap.get()はそのコレクションを変更します
  • 最も難しいケースは、例外isが複数のスレッドによる同時変更のためである場合です。

同時変更エラーを防ぐためのプログラミング

可能な場合は、Collectionオブジェクトへのすべての参照を制限することで、同時変更を防止しやすくなります。 Collectionprivateオブジェクトまたはローカル変数にし、メソッドからCollectionまたはその反復子への参照を返さないでください。その場合、allCollectionを変更できる場所を調べる方がはるかに簡単です。 Collectionを複数のスレッドで使用する場合は、適切な同期とロックを使用してのみスレッドがCollectionにアクセスするようにすることが実際的です。

9
Raedwald

Java同期の問題ではなく、データベースのロックの問題に似ています。

すべての永続クラスにバージョンを追加して整理できるかどうかはわかりませんが、Hibernateがテーブル内の行への排他的アクセスを提供できる方法の1つです。

分離レベルを高くする必要がある可能性があります。 「ダーティリード」を許可する場合は、おそらくシリアライズ可能にする必要があります。

2
duffymo

私のようにマップを繰り返しながらマップからいくつかのエントリを削除しようとしている場合、選択した回答は、変更前にコンテキストに直接適用できないことに注意してください。

ここでは、初心者が時間を節約できるように、実際の例を示します。

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();
    }
0
ZhaoGang

何をしようとしているかに応じて、CopyOnWriteArrayListまたはCopyOnWriteArraySetを試してください。

0
Javamann