2

私は StackOverflow を検索しましたが、多くの ConcurrentModificationException の質問があります。それらを読んだ後、私はまだ混乱しています。私はこれらの例外をたくさん受けています。オブジェクトを追跡するために「レジストリ」セットアップを使用しています。

public class Registry {
    public static ArrayList<Messages> messages = new ArrayList<Messages>();
    public static ArrayList<Effect> effects = new ArrayList<Effect>();
    public static ArrayList<Projectile> proj = new ArrayList<Projectile>();

    /** Clears all arrays */
    public static void recycle(){
        messages.clear();
        effects.clear();
        proj.clear();
    }
}

次のように ArrayLists にアクセスして、これらのリストにオブジェクトを追加および削除していますRegistry.effects.add(obj)Registry.effects.remove(obj)

再試行ループを使用して、いくつかのエラーを回避することができました。

//somewhere in my game..
boolean retry = true;
while (retry){
    try {
        removeEffectsWithSource("CHARGE");
        retry = false;
    }
catch (ConcurrentModificationException c){}
}

private void removeEffectsWithSource(String src) throws ConcurrentModificationException {
    ListIterator<Effect> it = Registry.effects.listIterator();
    while ( it.hasNext() ){
        Effect f = it.next();
        if ( f.Source.equals(src) ) {
            f.unapplyEffects();
            Registry.effects.remove(f);
        }
    }
}

しかし、それ以外の場合、これは実用的ではありません。drawProjectiles()何も変更していないにもかかわらず、メソッドで ConcurrentModificationExceptions を取得し続けます。犯人は、画面に触れた場合だと思います。これにより、新しい Projectile オブジェクトが作成され、 draw メソッドがまだ反復している間に Registry.proj に追加されます。

draw メソッドで再試行ループをうまく実行できないか、オブジェクトの一部を再描画します。だから今、私は新しい解決策を見つけることを余儀なくされています..私がやっていることを達成するためのより安定した方法はありますか?

ああ、私の質問のパート 2: 多くの人が (私が使用してきたように) ListIterators を使用することを提案していますが、私は理解できませんListIterator.remove()。イテレータ自体?

4

2 に答える 2

2

一番上の行、3 つの推奨事項:

  • 「ループで例外をラップする」ことをしないでください。例外は、制御フローではなく、例外的な条件に対するものです。( Effective Java #57 またはExceptions and Control Flowまたは Example of "using exceptions for control flow" )
  • Registry オブジェクトを使用する場合は、そのオブジェクトのアクセサメソッドではなく、スレッド セーフな動作メソッドを公開し、その単一のクラス内に同時実行の理由を含めます。あなたの人生は良くなります。 パブリック フィールドでコレクションを公開しない。(ええと、なぜそれらのフィールドがあるのですか?)static
  • 実際の並行性の問題を解決するには、次のいずれかを実行します。
    1. 同期コレクションを使用する (潜在的なパフォーマンス ヒット)
    2. 並行コレクションを使用する (複雑なロジックの場合もありますが、おそらく効率的です)
    3. スナップショットを使用する (おそらく隠れているsynchronizedReadWriteLock隠れている)

あなたの質問のパート1

マルチスレッドのシナリオでは同時データ構造を使用するか、シンクロナイザーを使用して防御コピーを作成する必要があります。おそらく、コレクションをpublicフィールドとして直接公開するのは間違っています。レジストリは、スレッドセーフな動作アクセサーをそれらのコレクションに公開する必要があります。たとえば、Registry.safeRemoveEffectBySource(String src)メソッドが必要な場合があります。スレッドの仕様をレジストリの内部に保持します。レジストリは、設計におけるこの集約情報の「所有者」と思われます。

おそらくセマンティクスは本当に必要ないのでList、これらを wraped に置き換えConcurrentHashMapsSetusingにすることをお勧めしCollections.newSetFromMap()ます。

メソッドは、a)セットのスナップショットを返すメソッドを使用draw()することができます。Registry.getEffectsSnapshot()またはb)Iterable<Effect> Registry.getEffects()安全な反復可能なバージョンを返すメソッドを使用します(おそらくConcurrentHashMap、どのような状況でもスローされないによってサポートされているだけCMEです)。描画ループがコレクションを変更する必要がない限り、ここでは (b) が望ましいと思います。これにより、ミューテーター スレッドとスレッド間の同期の保証が非常に弱くなりますが、スレッドが頻繁に実行されるとdraw()仮定するdraw()と、更新の欠落などはおそらく大したことではありません。

質問のパート 2

別の回答が示すように、シングル スレッドの場合、必ず を使用しIterator.remove()てアイテムを削除する必要がありますが、Registry可能であれば、このロジックをクラス内にラップする必要があります。場合によっては、コレクションをロックし、コレクションを反復処理して集計情報を収集し、反復が完了した後に構造を変更する必要があります。remove()メソッドが単にそれをバッキング コレクションから削除するのか、それともバッキング コレクションから削除するのかを尋ねますIterator...基になるコレクションからオブジェクトを削除することを示すAPI コントラクトを参照してください。Iterator.remove()このSOの質問も参照してください。

于 2010-11-05T02:07:31.283 に答える
1

コレクションを繰り返し処理している間は、アイテムをコレクションから直接削除することはできませんConcurrentModificationException

解決策は、ヒントとして、remove代わりに Iterator でメソッドを呼び出すことです。これにより、基になるコレクションからも削除されますが、イテレーターが何が起こっているかを認識し、コレクションが変更されたことを検出したときに例外をスローしないように削除されます。

于 2010-11-05T01:54:29.193 に答える