简体   繁体   English

为什么这段代码会抛出 java ConcurrentModificationException?

[英]Why does this code throw a 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();
     }

   }
}

This code throws a ConcurrentModificationException But I thought if they are both synchronized on the listenersMutex then they should be executed in serial?此代码引发 ConcurrentModificationException 但我认为如果它们都在 listenersMutex 上同步,那么它们应该串行执行吗? All code within functions that operate on the listeners list operate within syncrhonized blocks that are synchronized on the Mutex.在侦听器列表上运行的函数中的所有代码都在在 Mutex 上同步的同步块中运行。 The only code that modifies the list are addNetworkListener(...) and removeNetworkListener(...) but removeNetworkListener is never called at the moment.修改列表的唯一代码是 addNetworkListener(...) 和 removeNetworkListener(...) 但此时永远不会调用 removeNetworkListener。

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.该错误似乎发生的是,在 onLogin 函数/线程迭代侦听器时仍在添加 NetworkClientListener。

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.编辑: NetworkClientListener 是一个接口,将“onLogin”的实现留给实现 function 的编码器,但他们对 function 的实现无权访问侦听器列表。

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.此外,我刚刚完全重新检查,并且在 addNetworkListener() 和 removeNetworkListener() 函数之外没有修改列表,其他函数只迭代列表。 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) 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!特别是 MJB 和 jprete!

Answer: Someone's implementation of onLogin() added a new listener to the gateway.答:有人实现 onLogin() 为网关添加了一个新的侦听器。 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.因此(由于 java 的同步是基于线程的并且是可重入的,因此线程可能不会锁定自身)当我们在他的实现中调用 onLogin() 时,我们正在遍历侦听器并在这样做的过程中添加一个新听众。

Solution: MJB's suggestion to use CopyOnWriteArrayList instead of synchronized lists解决方案:MJB 建议使用 CopyOnWriteArrayList 而不是同步列表

Mutexes only guard from access from multiple threads.互斥锁仅防止来自多个线程的访问。 If nl.onLogin() happens to have logic that adds a listener to the listeners list, then a ConcurrentModificationException may be thrown, because it's being accessed (by the iterator) and changed (by the add) simultaneously.如果nl.onLogin()恰好具有将侦听器添加到listeners器列表的逻辑,则可能会引发ConcurrentModificationException ,因为它同时被(通过迭代器)访问和更改(通过添加)。

EDIT: Some more information would probably help.编辑:更多信息可能会有所帮助。 As I recall, Java collections check for concurrent modifications by keeping a modification count for each collection.我记得,Java collections 通过保持每个集合的修改计数来检查并发修改。 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.如果计数发生变化,则集合在访问点而不是在修改点抛出ConcurrentModificationException For iterators, it checks the counter after every call to next() , so on the next iteration of the loop through listeners , you should see the exception.对于迭代器,它会在每次调用next()后检查计数器,因此在通过listeners循环的一次迭代中,您应该会看到异常。

I must admit that I don't see it either - if indeed removeListeners is not called.我必须承认我也没有看到它——如果确实没有调用 removeListeners。

What is the logic of the nl.onLogin bit? nl.onLogin 位的逻辑是什么? If it modified stuff, it could cause the exception.如果它修改了东西,它可能会导致异常。

A tip btw if you expect listeners to be moderately rare in being added, you could make the list CopyOnWriteArrayList type -- in which case you don't need your mutexes at all - CopyOnWriteArrayList is totally thread safe, and returns a weakly consistent iterator that will never throw CME (except where I just said, in nl.onLogin).顺便说一句,如果您希望添加的侦听器很少见,则可以将列表设为 CopyOnWriteArrayList 类型——在这种情况下,您根本不需要互斥锁——CopyOnWriteArrayList 是完全线程安全的,并返回一个弱一致的迭代器永远不会抛出 CME(除了我刚才在 nl.onLogin 中所说的)。

Instead of ArrayList, use can use thread-safe class CopyOnWriteArrayList which does not throw ConcurrentModificationException even if it is modified while iterating.代替 ArrayList,可以使用线程安全的 class CopyOnWriteArrayList,即使在迭代时修改它也不会抛出 ConcurrentModificationException。 While iterating if it is attempted to modify(add,update) then it makes a copy of the list, but iterater will continue working on original one.如果尝试修改(添加,更新)进行迭代,则它会制作列表的副本,但迭代器将继续处理原始列表。

Its a bit slower than ArrayList.它比 ArrayList 慢一点。 It is useful in cases where you do not want to syncronise the iterations.它在您不想同步迭代的情况下很有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM