クラスのIterator
forHashSet
は、フェイルファストイテレータです。HashSet
クラスのドキュメントから:
このクラスのイテレータメソッドによって返されるイテレータはフェイルファストです。イテレータが作成された後、イテレータ自体のremoveメソッド以外の方法でセットが変更された場合、イテレータはConcurrentModificationExceptionをスローします。したがって、同時変更に直面した場合、イテレータは、将来の不確定な時点で任意の非決定的な動作のリスクを冒すのではなく、迅速かつクリーンに失敗します。
イテレータのフェイルファスト動作は保証できないことに注意してください。一般的に言えば、同期されていない同時変更が存在する場合、ハード保証を行うことは不可能です。フェイルファストイテレータは、ベストエフォートベースでConcurrentModificationExceptionをスローします。したがって、その正確性をこの例外に依存するプログラムを作成するのは誤りです。イテレータのフェイルファスト動作は、バグを検出するためにのみ使用する必要があります。
最後の文に注意してください-あなたがキャッチしているという事実はConcurrentModificationException
、別のスレッドがコレクションを変更していることを意味します。同じJavadocAPIページにも次のように記載されています。
複数のスレッドが同時にハッシュセットにアクセスし、少なくとも1つのスレッドがセットを変更する場合は、外部で同期する必要があります。これは通常、セットを自然にカプセル化するオブジェクトを同期することによって実現されます。そのようなオブジェクトが存在しない場合は、Collections.synchronizedSetメソッドを使用してセットを「ラップ」する必要があります。これは、セットへの偶発的な非同期アクセスを防ぐために、作成時に行うのが最適です。
Set s = Collections.synchronizedSet(new HashSet(...));
Javadocへの参照は、次に何をすべきかについて自明であると思います。
ImmutableSet
さらに、あなたの場合、オブジェクトにHashSetを作成する代わりに、を使用していない理由がわかりません(これは、暫定的に変更される可能性があります。メソッドterms
の実装はわかりませんが、その予感があります。getTerms
基になるキーセットが変更されています)。不変のセットを作成すると、現在のスレッドが元のキーセットの独自の防御コピーを持つことができます。
同期セットを使用することでaConcurrentModificationException
を防ぐことができますが(Java APIドキュメントに記載されているように)、すべてのスレッドがバッキングコレクションではなく、同期コレクションに直接アクセスすることが前提条件であることに注意してください(これは、HashSet
おそらく1つのスレッドで作成されますが、の基になるコレクションはMultiMap
他のスレッドによって変更されます)。同期されたコレクションクラスは、実際には、スレッドがアクセスを取得するための内部ミューテックスを維持します。他のスレッドからミューテックスに直接アクセスすることはできないため(ここでアクセスするのは非常にばかげています)、クラスのメソッドを使用して、キーセットまたはMultiMap自体の防御コピーを使用することを検討する必要があります。unmodifiableMultimap
MultiMaps
(getTermsメソッドから変更不可能なMultiMapを返す必要があります)。同期されたMultiMapを返す必要性を調査することもできますが、その場合も、基になるコレクションを同時変更から保護するために、ミューテックスをスレッドによって取得する必要があることを確認する必要があります。
実際のコレクションへの同時アクセスが保証されるかどうかわからないという唯一の理由で、スレッドセーフHashSet
の使用について言及することを意図的に省略していることに注意してください。ほとんどの場合、そうではありません。
編集:シングルスレッドシナリオでConcurrentModificationException
スローされますIterator.next
if(c.isSomething()) C.remove(c);
これは、編集された質問で紹介されたステートメントに関するものです。
呼び出すCollection.remove
と、質問の性質が変わりますConcurrentModificationException
。これは、シングルスレッドのシナリオでもsをスローできるようになるためです。
可能性は、メソッド自体の使用と、Collection
'イテレータの使用、この場合it
はステートメント:を使用して初期化された変数の使用から生じますIterator<BooleanClause> it = C.iterator();
。
これは、の現在の状態に関連するストアの状態Iterator
it
を繰り返し処理します。この特定のケース(Sun / Oracle JREを想定)では、 (によって使用されるクラスの内部内部クラス)を使用して、を反復処理します。これの特別な特徴は、 (この場合は)そのメソッドを介して実行された構造変更の数を追跡することです。Collection
C
Collection
KeyIterator
HashMap
HashSet
Collection
Iterator
Collection
HashMap
Iterator.remove
直接呼び出しremove
てCollection
から、の呼び出しでフォローアップするとIterator.next
、イテレータは、が認識していない構造上の変更が発生したかどうかConcurrentModificationException
を確認するために、をスローします。この場合、は構造の変更を引き起こします。これは、によって追跡されますが、によっては追跡されません。Iterator.next
Collection
Iterator
Collection.remove
Collection
Iterator
問題のこの部分を克服するには、を呼び出す必要がIterator.remove
ありますCollection.remove
。これにより、Iterator
がの変更を認識できるようになりCollection
ます。このIterator
場合、remove
メソッドを通じて発生する構造変更を追跡します。したがって、コードは次のようになります。
final Multimap<Term, BooleanClause> terms = getTerms(bq);
for (Term t : terms.keySet()) {
Collection<BooleanClause> C = new HashSet(terms.get(t));
if (!C.isEmpty()) {
for (Iterator<BooleanClause> it = C.iterator(); it.hasNext();) {
BooleanClause c = it.next();
if(c.isSomething()) it.remove(); // <-- invoke remove on the Iterator. Removes the element returned by it.next.
}
}
}