为什么这段代码会抛出 java ConcurrentModificationException?
public final class ClientGateway {
private static ClientGateway instance;
private static List<NetworkClientListener> listeners = Collections.synchronizedList(new ArrayList<NetworkClientListener>());
private static final Object listenersMutex = new Object();
protected EventHandler eventHandler;
private ClientGateway() {
eventHandler = new EventHandler();
}
public static synchronized ClientGateway getInstance() {
if (instance == null)
instance = new ClientGateway();
return instance;
}
public void addNetworkListener(NetworkClientListener listener) {
synchronized (listenersMutex) {
listeners.add(listener);
}
}
class EventHandler {
public void onLogin(final boolean isAdviceGiver) {
new Thread() {
public void run() {
synchronized (listenersMutex) {
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
}
}
}.start();
}
}
}
此代码抛出 ConcurrentModificationException 但我想如果它们都在listenersMutex上同步那么它们应该串行执行?在侦听器列表上操作的函数内的所有代码都在互斥体上同步的同步块内操作。修改列表的唯一代码是addNetworkListener(...) 和removeNetworkListener(...),但目前从未调用removeNetworkListener。
该错误似乎是在 onLogin 函数/线程迭代侦听器时仍在添加 NetworkClientListener。
感谢您的见解!
编辑: NetworkClientListener 是一个接口,并将“onLogin”的实现留给实现该函数的编码器,但他们的函数实现无法访问侦听器列表。
另外,我刚刚完全重新检查,除了 addNetworkListener() 和 removeNetworkListener() 函数之外没有对列表进行任何修改,其他函数仅迭代列表。将代码从: 更改
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
为:
for(int i = 0; i < listeners.size(); i++)
nl.onLogin(isAdviceGiver);
似乎可以解决并发问题,但我已经知道这一点,并且想首先知道是什么原因造成的。
再次感谢您的持续帮助!
例外: 线程“Thread-5”中的异常 java.util.ConcurrentModificationException 在 java.util.ArrayList$Itr.checkForCommodification(ArrayList.java:782) 在 java.util.ArrayList$Itr.next(ArrayList.java:754) 在 chapchat.client.networkcommunication.ClientGateway$EventHandler$5.run(ClientGateway.java:283)
编辑 好吧,我觉得有点愚蠢。但感谢您的所有帮助!特别是 MJB 和jprete!
答案:某人的 onLogin() 实现向网关添加了一个新的侦听器。因此(因为java的同步是基于线程并且是可重入的,因此线程可能不会锁定自身)当我们在他的实现中调用onLogin()时,我们正在迭代监听器,并在这样做的过程中添加一个新听众。
解决方案:MJB建议使用CopyOnWriteArrayList代替同步列表
public final class ClientGateway {
private static ClientGateway instance;
private static List<NetworkClientListener> listeners = Collections.synchronizedList(new ArrayList<NetworkClientListener>());
private static final Object listenersMutex = new Object();
protected EventHandler eventHandler;
private ClientGateway() {
eventHandler = new EventHandler();
}
public static synchronized ClientGateway getInstance() {
if (instance == null)
instance = new ClientGateway();
return instance;
}
public void addNetworkListener(NetworkClientListener listener) {
synchronized (listenersMutex) {
listeners.add(listener);
}
}
class EventHandler {
public void onLogin(final boolean isAdviceGiver) {
new Thread() {
public void run() {
synchronized (listenersMutex) {
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
}
}
}.start();
}
}
}
This code throws a ConcurrentModificationException
But I thought if they are both synchronized on the listenersMutex then they should be executed in serial? All code within functions that operate on the listeners list operate within syncrhonized blocks that are synchronized on the Mutex. The only code that modifies the list are addNetworkListener(...) and removeNetworkListener(...) but removeNetworkListener is never called at the moment.
What appears to be happening with the error is that a NetworkClientListener is still being added while the onLogin function/thread is iterating the listeners.
Thank you for your insight!
EDIT: NetworkClientListener is an interface and leaves the implementation of "onLogin" up to the coder implementing the function, but their implementation of the function does not have access to the listeners List.
Also, I just completely rechecked and there is no modification of the list outside of the addNetworkListener() and removeNetworkListener() functions, the other functions only iterate the list. Changing the code from:
for (NetworkClientListener nl : listeners)
nl.onLogin(isAdviceGiver);
To:
for(int i = 0; i < listeners.size(); i++)
nl.onLogin(isAdviceGiver);
Appears to solve the concurrency issue, but I already knew this and would like to know what's causing it in the first place.
Thanks again for your continuing help!
Exception:
Exception in thread "Thread-5" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:782)
at java.util.ArrayList$Itr.next(ArrayList.java:754)
at chapchat.client.networkcommunication.ClientGateway$EventHandler$5.run(ClientGateway.java:283)
EDIT Okay, I feel a little dumb. But thank you for all your help! Particularly MJB & jprete!
Answer: Someone's implementation of onLogin() added a new listener to the gateway. Therefore(since java's synchronization is based on Threads and is reentrant, so that a Thread may not lock on itself) when onLogin() was called we in his implementation, we were iterating through the listeners and in the middle of doing so, adding a new listener.
Solution: MJB's suggestion to use CopyOnWriteArrayList instead of synchronized lists
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
发布评论
评论(3)
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
互斥体仅防止来自多个线程的访问。如果
nl.onLogin()
碰巧具有将侦听器添加到listeners
列表的逻辑,则可能会抛出ConcurrentModificationException
,因为它正在被同时访问(通过迭代器)和更改(通过添加)。编辑:更多信息可能会有所帮助。我记得,Java 集合通过保留每个集合的修改计数来检查并发修改。每次执行更改集合的操作时,计数都会增加。为了检查操作的完整性,在操作开始和结束时检查计数;如果计数发生变化,则集合会在访问时抛出 ConcurrentModificationException,而不是在修改时抛出。对于迭代器,它会在每次调用
next()
后检查计数器,因此在通过listeners
循环的下一个迭代中,您应该看到例外。Mutexes only guard from access from multiple threads. If
nl.onLogin()
happens to have logic that adds a listener to thelisteners
list, then aConcurrentModificationException
may be thrown, because it's being accessed (by the iterator) and changed (by the add) simultaneously.EDIT: Some more information would probably help. As I recall, Java collections check for concurrent modifications by keeping a modification count for each collection. Every time you do an operation that changes the collection, the count gets incremented. In order to check the integrity of operations, the count is checked at the beginning and end of the operation; if the count changed, then the collection throws a
ConcurrentModificationException
at the point of access, not at the point of modification. For iterators, it checks the counter after every call tonext()
, so on the next iteration of the loop throughlisteners
, you should see the exception.