当另一个线程在removeListener()中使用迭代器时,为什么以下代码中的ConcurrentModificationException调用会引发fireEvent()

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
            }
        }
    }
}

据我了解,由于我在synchronized (synchronizedListeners)中使用了fireEvent(),因此这应该阻塞任何其他调用removeListener()的线程,直到fireEvent()中的迭代完成为止,此时从该Set中删除元素应该是安全的。但这似乎并非如此。我究竟做错了什么?

可能相关:Java synchronized block vs. 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();
}

最佳答案

我无法重现您所描述的内容-底部的代码提供了如下所示的输出-该内容显示remove在迭代的中间被调用,但是直到您迭代之后才完成,因为您使用的是同步集合。这就是人们所期望的行为,并且不会引发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());
    }
}

10-08 13:55