2

別のスレッドが でイテレータを使用してremoveListener()いるときに、次のコードで を呼び出すと がスローされるのはなぜですか?ConcurrentModificationExceptionfireEvent()

public class MyClass {

    private Set<Object> synchronizedListeners;

    public MyClass() {
        synchronizedListeners = Collections.synchronizedSet(
                new LinkedHashSet<Object>());
    }

    public void addListener(Object listener) {
        synchronizedListeners.add(listener);
    }

    public synchronized void removeListener(Object listener) {
        synchronizedListeners.remove(listener);
    }

    public void fireEvent() {
        synchronized (synchronizedListeners) {
            for (Object listener : synchronizedListeners) {
                // do something with listener
            }
        }
    }
}

私の理解では、 in を使用synchronized (synchronizedListeners)しているため、 infireEvent()を呼び出す他のスレッドをブロックする必要がremoveListener()あります。 in の反復fireEvent()が完了するまで、この Set から要素を安全に削除する必要があります。しかし、そうではないようです。私は何を間違っていますか?

おそらく関連: Java 同期ブロックと Collections.synchronizedMap

編集: removeListener() メソッドを不必要に同期していたことが指摘されました。だから私はこのバージョンを試しました:

public void removeListener(Object listener) {
    synchronizedListeners.remove(listener);
}

しかし、それでも同じエラーが発生しました。

編集 2: assylias が指摘したように、上記のコードでは問題は見えません。エラーの原因となっていremoveListener()たブロックの for ループ内から呼び出していました。synchronized (synchronizedListeners)この場合に最終的に使用した修正は、リスナーを別のスレッドから削除することです。

public void removeListener(final Object listener) {
    new Thread() {
        @Override
        public void run() {
            synchronizedListeners.remove(listener);
        }
    }.start();
}
4

2 に答える 2

4

2 つの異なるオブジェクトで同期しています。

removeListenerメソッドはインスタンスで同期されますMyClassが、 内のループfireEventはセットで同期さsynchronizedListenersれます。

必要なことはsynchronizedListeners、セット自体で を使用するすべてのメソッドを同期することです。

于 2012-08-07T09:35:44.547 に答える
3

あなたが説明したことを再現できません-下部のコードは以下に示す出力を提供します-これは、削除が反復の途中で呼び出されたことを示していますが、同期されたコレクションを使用しているため、反復後まで完了しません。これは予想される動作であり、ConcurrentModificationException はスローされません。ここでは役に立たないためsynchronized、メソッドからキーワードを削除したことに注意してください。removeListener

fire 100000
remove 100000
done fire 100000
done remove 99999

結論: 問題は別の場所にあります。たとえば、fireEvent メソッドをオーバーライドするサブクラスがある場合、または投稿したコードとまったく同じではない同期セットを作成した場合などです。

public static void main(String[] args) {
    final MyClass mc = new MyClass();
    final Object o = new Object();
    mc.addListener(o);
    for (int i = 0; i < 99999; i++) {
        Object o1 = new Object();
        mc.addListener(o1);
    }
    Runnable remove = new Runnable() {

        @Override
        public void run() {
            mc.removeListener(o);
        }
    };

    new Thread(remove).start();
    mc.fireEvent();
}

public static class MyClass {

    protected Set<Object> synchronizedListeners = Collections.synchronizedSet(new LinkedHashSet<Object>());

    public void addListener(Object listener) {
        synchronizedListeners.add(listener);
    }

    public void removeListener(Object listener) {
        System.out.println("remove " + synchronizedListeners.size());
        synchronizedListeners.remove(listener);
        System.out.println("done remove " + synchronizedListeners.size());
    }

    public void fireEvent() {
        System.out.println("fire " + synchronizedListeners.size());
        synchronized (synchronizedListeners) {
            for (Object listener : synchronizedListeners) {
                // do something with listener
            }
        }
        System.out.println("done fire "  + synchronizedListeners.size());
    }
}
于 2012-08-07T09:57:33.503 に答える