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


130

私はCollectionHashMapJPAによって間接的に使用されているため、そうなります)を使用していますが、明らかにランダムにコードがをスローし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)

1
もう少しコンテキストを提供できますか?エンティティをマージ、更新、または削除していますか?このエンティティにはどのような関連付けがありますか?カスケード設定はどうですか?
ordnungswidrig

1
スタックトレースから、HashMapの反復中に例外が発生していることがわかります。確かに他のいくつかのスレッドがマップを変更していますが、例外は反復しているスレッドで発生します。
Chochos 2010

回答:


263

これは同期の問題ではありません。これは、反復処理されている基になるコレクションがIterator以外の何かによって変更された場合に発生します。

Iterator it = map.entrySet().iterator();
while (it.hasNext())
{
   Entry item = it.next();
   map.remove(item.getKey());
}

これはConcurrentModificationExceptionit.hasNext()が2回目に呼び出されたときにをスローします。

正しいアプローチは

   Iterator it = map.entrySet().iterator();
   while (it.hasNext())
   {
      Entry item = it.next();
      it.remove();
   }

このイテレータがremove()操作をサポートしていると仮定します。


1
おそらく、Hibernateが反復処理を行っているように見えます。これは、適切に正しく実装する必要があります。マップを変更するコールバックがあるかもしれませんが、それはありそうにありません。予測不可能性は、実際の同時実行性の問題を示しています。
トム・ホーティン-タックライン2009年

この例外は、スレッドの同時実行性とは何の関係もありません。これは、変更されるイテレーターのバッキングストアによって発生します。別のスレッドによるものかどうかは、イテレータには関係ありません。私見それは原因の不正確な印象を与えるので、名前の付いた例外ではありません。
ロビン

ただし、予測できない場合は、この例外の条件を発生させるスレッド化の問題が発生している可能性が高いことに同意します。これは、例外名のために、それをさらに混乱させます。
ロビン

これは正解であり、承認された回答よりも優れた説明ですが、承認された回答は素晴らしい修正です。イテレータの内部であっても、ConcurrentHashMapはCMEの影響を受けません(ただし、イテレータは依然としてシングルスレッドアクセス用に設計されています)。
G__

マップにはiterator()メソッドがないため、このソリューションには意味がありません。ロビンの例は、リストなどに適用できます。
ピーター

72

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


それで本当に問題は解決しましたか?同じ問題が発生していますが、スレッドの問題はすべて除外できます。
tobiasbayer 2010

5
別の解決策は、マップのコピーを作成し、代わりにそのコピーを反復処理することです。または、キーのセットをコピーして反復し、元のマップから各キーの値を取得します。
Chochos 2010

コレクションを繰り返し処理するのはHibernateなので、単純にコピーすることはできません。
tobiasbayer

1
インスタント救世主。なぜこれがうまく機能したのかを調べてみると、これから先にさらに驚きはありません。
Valchris 2011年

1
同期の問題ではないと思いますが、同じオブジェクトをループしているときに同じ変更を変更すると問題になります。
Rais Alam

17

修正Collectionということを反復しながら、Collection使用がIteratorされて許可されていないのほとんどがCollectionクラス。Javaライブラリーは、Collection「並行変更」と繰り返し実行しながら、aを変更する試みを呼び出します。残念ながら、唯一の考えられる原因は複数のスレッドによる同時変更であることが示唆されていますが、そうではありません。1つのスレッドのみを使用してCollectionCollection.iterator()、または拡張forループを使用して)のイテレータを作成し、反復を開始(を使用Iterator.next()、または拡張forループの本体に入る)し、を変更してCollection、反復を続行できます。

プログラマーを支援するために、これらのクラスの一部の実装は、誤った並行変更を検出しようとし、それを検出した場合にをスローします。ただし、一般に、すべての同時変更の検出を保証することは不可能であり、実用的ではありません。したがって、を誤って使用しても、常にスローされるとは限りません。CollectionConcurrentModificationExceptionCollectionConcurrentModificationException

のドキュメントはConcurrentModificationException言う:

この例外は、オブジェクトの同時変更が許可されていないときに、オブジェクトの同時変更を検出したメソッドによってスローされる場合があります...

この例外は、オブジェクトが別のスレッドによって同時に変更されたことを常に示すわけではないことに注意してください。単一のスレッドがオブジェクトのコントラクトに違反する一連のメソッド呼び出しを発行した場合、オブジェクトはこの例外をスローする可能性があります...

フェイルファストの動作は保証されないことに注意してください。これは、一般的に言えば、非同期の同時変更が存在する場合にハードな保証を行うことは不可能であるためです。フェイルファースト操作ConcurrentModificationExceptionは、ベストエフォートベースでスローされます。

ご了承ください

文書HashSetHashMapTreeSetおよびArrayListクラスがこれを言います:

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

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

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

Mapインターフェイスのいくつかのメソッドのドキュメントはこれを言います:

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

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

デバッグ中 ConcurrentModificationException

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

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

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

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


シングルスレッドの場合、同時変更が許可されないのはなぜですか。単一のスレッドが通常のハッシュマップで同時変更を実行できる場合、どのような問題が発生する可能性がありますか?
MasterJoe

4

Java 8では、ラムダ式を使用できます。

map.keySet().removeIf(key -> key condition);

2

Javaの同期の問題ではなく、データベースのロックの問題に聞こえます。

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

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


彼らはハッシュテーブルを意味すると思います。JDK 1.0の一部として出荷されました。Vectorと同様に、スレッドセーフで低速であるように作成されています。どちらも、非スレッドセーフの代替手段であるHashMapとArrayListに取って代わられました。使用した分を支払います。
duffymo 2014年

0

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


0

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

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

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

リストから最後のx個のアイテムを削除しようとすると、この例外が発生しました。 myList.subList(lastIndex, myList.size()).clear();私のために働いた唯一の解決策でした。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.