Why does a call to removeListener() in the following code throw a ConcurrentModificationException when another thread is using the iterator in 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
}
}
}
}
From my understanding, since I’m using synchronized (synchronizedListeners) in fireEvent(), this should block any other thread that calls removeListener(), till the iteration in fireEvent() is complete at which point it should be safe to remove an element from this Set. But this doesn’t seem to be the case. What am I doing wrong?
Possibly related: Java synchronized block vs. Collections.synchronizedMap
Edit: It was pointed out that I was unnecessarily synchronizing the removeListener() method. So I tried this version:
public void removeListener(Object listener) {
synchronizedListeners.remove(listener);
}
But still got the same error.
Edit 2: As assylias pointed out, the problem isn’t visible in the above code. I was calling removeListener() from inside the for loop in the synchronized (synchronizedListeners) block which was causing the error. The fix I ended up using in this case is to remove the listener from another thread:
public void removeListener(final Object listener) {
new Thread() {
@Override
public void run() {
synchronizedListeners.remove(listener);
}
}.start();
}
I can’t reproduce what you describe – the code at the bottom gives the output shown below – which shows that remove is called in the middle of the iteration, but does not complete until after the iteration because you use a synchronized collection. That’s the behaviour one would expect and no ConcurrentModificationException is thrown. Note that I have removed the
synchronizedkeyword from theremoveListenermethod as it is useless here.Conclusion: the problem is somewhere else. For example, if you have a subclass that overrides the fireEvent method, or if you construct the synchronized set not exactly as in the code you posted.