繁体   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();
     }

   }
}

此代码引发 ConcurrentModificationException 但我认为如果它们都在 listenersMutex 上同步,那么它们应该串行执行吗? 在侦听器列表上运行的函数中的所有代码都在在 Mutex 上同步的同步块中运行。 修改列表的唯一代码是 addNetworkListener(...) 和 removeNetworkListener(...) 但此时永远不会调用 removeNetworkListener。

该错误似乎发生的是,在 onLogin 函数/线程迭代侦听器时仍在添加 NetworkClientListener。

感谢您的见解!

编辑: NetworkClientListener 是一个接口,将“onLogin”的实现留给实现 function 的编码器,但他们对 function 的实现无权访问侦听器列表。

此外,我刚刚完全重新检查,并且在 addNetworkListener() 和 removeNetworkListener() 函数之外没有修改列表,其他函数只迭代列表。 从以下位置更改代码:

for (NetworkClientListener nl : listeners) 
   nl.onLogin(isAdviceGiver);

至:

for(int i = 0; i < listeners.size(); i++)
   nl.onLogin(isAdviceGiver);

似乎解决了并发问题,但我已经知道这一点,并想首先知道是什么原因造成的。

再次感谢您的持续帮助!

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)

编辑好吧,我觉得有点愚蠢。 但是感谢您的所有帮助! 特别是 MJB 和 jprete!

答:有人实现 onLogin() 为网关添加了一个新的侦听器。 因此(由于 java 的同步是基于线程的并且是可重入的,因此线程可能不会锁定自身)当我们在他的实现中调用 onLogin() 时,我们正在遍历侦听器并在这样做的过程中添加一个新听众。

解决方案:MJB 建议使用 CopyOnWriteArrayList 而不是同步列表

互斥锁仅防止来自多个线程的访问。 如果nl.onLogin()恰好具有将侦听器添加到listeners器列表的逻辑,则可能会引发ConcurrentModificationException ,因为它同时被(通过迭代器)访问和更改(通过添加)。

编辑:更多信息可能会有所帮助。 我记得,Java collections 通过保持每个集合的修改计数来检查并发修改。 每次您执行更改集合的操作时,计数都会增加。 为了检查操作的完整性,在操作的开始和结束时检查计数; 如果计数发生变化,则集合在访问点而不是在修改点抛出ConcurrentModificationException 对于迭代器,它会在每次调用next()后检查计数器,因此在通过listeners循环的一次迭代中,您应该会看到异常。

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

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

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

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

它比 ArrayList 慢一点。 它在您不想同步迭代的情况下很有用。

暂无
暂无

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

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