Java 使用迭代器的ConcurrentModificationException

Java 使用迭代器的ConcurrentModificationException,java,iterator,Java,Iterator,我使用迭代器在集合上循环,如下所示: Iterator<Entity> entityItr = entityList.iterator(); while (entityItr.hasNext()) { Entity curr = entityItr.next(); for (Component c : curr.getComponents()) { if (c instanceof Play

我使用迭代器在集合上循环,如下所示:

Iterator<Entity> entityItr = entityList.iterator(); 

    while (entityItr.hasNext())
    {
        Entity curr = entityItr.next();

        for (Component c : curr.getComponents())
        {
            if (c instanceof PlayerControlled)
            {
                ((PlayerControlled) c).pollKeyboard();  
            }
        }
    }
为什么在我没有改变任何东西的时候会发生这种情况

非常感谢

编辑-堆栈跟踪:

java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at cw.systems.Input.checkInputs(Input.java:31)
at cw.systems.Input.begin(Input.java:21)
at cw.misc.Game.render(Game.java:73)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:207)
at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)

看起来有另一个线程正在使用同一个集合,并在该代码迭代该集合时对其进行修改


您可以改用navite java。它们是线程安全的。然而,这是一个很好的习惯——它们是线程安全的,并强制您设计可靠的代码

您必须修改列表:

  • 在迭代器中的
    pollKeyboard
    方法中,不使用迭代器上的
    add
    remove
    方法;或
  • 在另一个线程中
  • 因此,您的异常是预期的行为。从,如果有一个线程正在迭代列表:

    如果在创建迭代器后的任何时候,以迭代器自己的remove或add方法以外的任何方式修改列表的结构,迭代器将抛出ConcurrentModificationException

     Entity curr = entityItr.next();
    
    如果多个线程同时使用该列表:

    请注意,此实现是不同步的。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改列表,它必须在外部进行同步

    解决方案: 如果只有一个线程访问列表,请确保使用或
    add
    方法修改列表

    对于多线程情况,如果没有可用的锁定对象,则可以使用

    首先将对列表的单个中心引用存储为:

    entityList = Collections.synchronizedList(theOriginalArrayList);
    
    然后通过以下方式访问它(与所有读卡器和写卡器一起):


    还有其他同步多线程访问的方法,包括将列表复制到数组(在同步块内)并对其进行迭代以进行读取,或者使用
    ReadWrite
    锁。它们都取决于您的确切需求。

    堆栈跟踪应该对此进行解释。读一读,然后贴出来。异常名称和javadoc也解释了这一点:您在对集合进行迭代时正在修改集合。您确定
    pollKeyboard
    无法以某种方式修改
    entityList
    吗?@SebastianRedl我认为如果
    pollKeyboard
    正在修改
    entityList
    ,那么异常将以某种方式从
    ((PlayerControl)c)抛出.poll键盘()非源于
    实体curr=entityItr.next()。对吗?@EpicPandaForce抛出此错误是为了防止更难发现的更严重的一致性问题。如果进入循环,但在访问元素
    i
    之前,在移除
    i
    之前的某个元素移动其他元素,该怎么办?您将跳过一个元素。使用迭代器允许您在对集合进行迭代时修改集合,只要您使用迭代器的方法修改它。不,迭代器不会再次重建。在每次迭代中,使用相同的迭代器对象来获取下一个元素。同时使用
    synchronizedList
    synchronized
    块似乎有些过分。@SebastianRedl。如果您只想调用
    add
    ,可以直接在返回的列表中执行,但对于迭代器,我将再次引用:“synchronizedList返回一个已同步的。。。列表所有访问必须通过返回的列表完成。。。当用户在返回的列表上进行迭代时,必须手动同步该列表。。。未能遵循此建议可能会导致不确定的行为“。在所有访问代码周围只有一个同步块就足够了。这使得额外的包装器确实有些过分。@MarkoTopolnik
    synchronizedList()
    允许您在不迭代时避免同步块,例如
    add
    在封面下执行自己的同步块。但是您仍然使用同步块进行迭代。因此,作为一般模式,您应该使用
    synchronizedList()
    ,在其上同步以进行迭代,并使用直接方法进行其他操作。是的,如果您有不同的同步对象并将其用于所有代码,则可以,但必须在每个操作上同步。当然。但更广泛的一点是,
    synchronizedList
    无法强制执行其公共API的线程安全性。事实上,您只获得了一个ev更复杂的契约:同步这个,不要同步那个,并且非常小心不要无意中将列表传递给第三方方法,而第三方方法可能会对列表进行迭代。
    synchronized (entityList) {
      // Readers might do:
      itr = entityList.iterator();
      while (i.hasNext())
        ... do stuff ...
    }