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

发布于 11-05 13:45 字数 2419 浏览 2 评论 0原文

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

_蜘蛛2024-11-12 13:45:00

互斥体仅防止来自多个线程的访问。如果 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 the listeners list, then a ConcurrentModificationException 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 to next(), so on the next iteration of the loop through listeners, you should see the exception.

微凉徒眸意2024-11-12 13:45:00

我必须承认我也没有看到它 - 如果确实没有调用removeListeners。

nl.onLogin 位的逻辑是什么?如果它修改了内容,则可能会导致异常。

顺便说一句,如果您希望添加侦听器的情况比较罕见,您可以将列表设为 CopyOnWriteArrayList 类型 - 在这种情况下,您根本不需要互斥体 - CopyOnWriteArrayList 是完全线程安全的,并返回一个弱一致的迭代器永远不会抛出 CME(除了我刚才说的,在 nl.onLogin 中)。

I must admit that I don't see it either - if indeed removeListeners is not called.

What is the logic of the nl.onLogin bit? 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).

烏雲後面有陽光2024-11-12 13:45:00

使用线程安全类 CopyOnWriteArrayList 代替 ArrayList ,即使在迭代时修改它也不会抛出 ConcurrentModificationException 。在迭代时,如果尝试修改(添加、更新),则会生成列表的副本,但迭代器将继续处理原始列表。

它比 ArrayList 慢一点。当您不想同步迭代时,它非常有用。

Instead of ArrayList , use can use thread-safe class CopyOnWriteArrayList which does not throw ConcurrentModificationException even if it is modified while iterating. 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 . It is useful in cases where you do not want to syncronise the iterations.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文