首页 文章

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

提问于
浏览 1859
3
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 .

感谢您的见解!

EDIT: NetworkClientListener是一个接口,并将"onLogin"的实现留给实现该函数的编码器,但是它们的函数实现无法访问侦听器List .

此外,我只是完全重新检查,并且没有修改addNetworkListener()和removeNetworkListener()函数之外的列表,其他函数只迭代列表 . 更改代码:

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

至:

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

似乎解决了并发问题, but I already knew this and would like to know what's causing it in the first place.

再次感谢您的继续帮助!

异常:java.util.ArrayList中的线程“Thread-5”java.util.ConcurrentModificationException中的异常$ Itr.checkForComodification(ArrayList.java:782)java.util.ArrayList $ Itr.next(ArrayList.java:754)at chapchat.client.networkcommunication.ClientGateway $事件处理$ 5.run(ClientGateway.java:283)

EDIT 好吧,我觉得有点傻 . 但是,谢谢你的帮助!特别是MJB&jprete!

答:某人对onLogin()的实现为网关添加了一个新的监听器 . 因此(因为java的同步是基于线程并且是可重入的,因此线程可能无法自行锁定)当onLogin()被调用时,我们在他的实现中,我们正在迭代监听器并在这样做的过程中,添加一个新听众 .

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

3 回答

  • 3

    互斥锁只能防止来自多个线程的访问 . 如果 nl.onLogin() 恰好具有向 listeners 列表添加侦听器的逻辑,则可能会抛出 ConcurrentModificationException ,因为它正在被(通过迭代器)访问并且同时更改(通过添加) .

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

  • 2

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

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

    如果你希望听众在添加时适度少见,你可以创建列表CopyOnWriteArrayList类型 - 在这种情况下你根本不需要你的互斥锁 - CopyOnWriteArrayList是完全线程安全的,并返回一个弱一致的迭代器,永远不会抛出CME(除了我刚刚说过的,在nl.onLogin中) .

  • 1

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

    它比ArrayList慢一点 . 在您不希望同步迭代的情况下,它非常有用 .

相关问题