当另一个线程在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());
}
}