2

为什么当另一个线程正在使用迭代器时removeListener(),以下代码中的调用会抛出 a ?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
            }
        }
    }
}

据我了解,由于我使用的是synchronized (synchronizedListeners)in fireEvent(),这应该阻止任何其他调用 的线程removeListener(),直到迭代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

您正在对两个不同的对象进行同步。

removeListener方法在MyClass实例上同步,而内部的循环在设置fireEvent上同步synchronizedListeners

您需要做的是同步使用synchronizedListeners集合本身的每个方法。

于 2012-08-07T09:35:44.547 回答
3

我无法重现您所描述的内容-底部的代码给出了如下所示的输出-这表明 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());
    }
}
于 2012-08-07T09:57:33.503 回答