【问题标题】:ConcurrentModificationException using Iterator使用迭代器的 ConcurrentModificationException
【发布时间】:2015-01-18 11:56:24
【问题描述】:

我正在使用迭代器来循环一个集合,如下所示:

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

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

        for (Component c : curr.getComponents())
        {
            if (c instanceof PlayerControlled)
            {
                ((PlayerControlled) c).pollKeyboard();  
            }
        }
    }

但是在下面一行我得到一个 ConcurrentModificationException

 Entity curr = entityItr.next();

为什么在我没有改变任何东西时会发生这种情况?

非常感谢

编辑 - 堆栈跟踪:

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)

【问题讨论】:

  • 堆栈跟踪应该解释它。阅读它,然后发布它。异常名称和 javadoc 也说明了这一点:您正在修改集合,同时对其进行迭代。
  • 你确定pollKeyboard不能修改entityList吗?
  • @SebastianRedl 我认为如果 pollKeyboard 正在修改 entityList 会以某种方式从 ((PlayerControlled) c).pollKeyboard(); 而不是 Entity curr = entityItr.next(); 抛出异常。对吗?
  • @EpicPandaForce 抛出此错误是为了防止更严重的并发问题,这些问题可能更难找到。如果您进入循环,但在您访问元素 i 之前的某个元素之前,i 被移除并转移其他元素怎么办?你最终会跳过一个元素。
  • 使用迭代器可以在对集合进行迭代的同时对其进行修改,只要您使用迭代器的方法对其进行修改即可。不,迭代器不会再次重建。在每次迭代中,相同的迭代器对象用于获取下一个元素。

标签: java iterator


【解决方案1】:

您必须修改列表:

  1. pollKeyboard 方法中的迭代器中,不使用迭代器上的addremove 方法;或
  2. 在另一个线程中

因此,您的例外是预期的行为。从the docs,如果你有一个单线程迭代列表:

如果列表在迭代器创建后的任何时间被结构性修改,除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器都会抛出 ConcurrentModificationException

如果多个线程同时使用该列表:

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

解决方案:

如果只有一个线程访问列表,请确保使用entityItr.removeadd 方法来修改列表。

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

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

entityList = Collections.synchronizedList(theOriginalArrayList);

然后访问它(与所有读者和作者):

synchronized (entityList) {
  // Readers might do:
  itr = entityList.iterator();
  while (i.hasNext())
    ... do stuff ...
}

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

【讨论】:

  • 同时拥有 synchronizedListsynchronized 块似乎有点矫枉过正。
  • @SebastianRedl。如果你只是想调用add,你可以直接在返回的列表上这样做,但对于迭代器,我会再次引用the docs:“synchronizedList 返回一个同步的......列表。......所有访问都必须通过返回的列表来完成... 当迭代它时,用户必须手动同步返回的列表...如果不遵循这个建议可能会导致不确定的行为”。
  • 在所有访问代码周围有一个只是同步块就足够了。这使得额外的包装器确实有点矫枉过正。
  • @MarkoTopolnik synchronizedList() 允许您在不迭代时避免同步块,因为add 等方法在幕后执行自己的同步块。但是,您仍然使用同步块进行迭代。因此,作为一般模式,您应该使用synchronizedList(),对其进行同步以进行迭代,并使用直接方法进行其他操作。是的,如果您有不同的同步对象并将其用于所有代码,但您必须在每个操作上同步。
  • 当然。但更广泛的一点是synchronizedList 无法强制其公共 API 的线程安全。事实上,你只会得到一个更复杂的合约:同步这个,不要同步那个,并且非常小心不要无意中将列表传递给可能会迭代它的第 3 方方法。
【解决方案2】:

看起来有另一个线程在使用相同的集合并在此代码迭代集合时对其进行修改。

ConcurrentModificationException

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-11-11
    • 1970-01-01
    • 2014-05-23
    • 2013-09-26
    • 2017-10-18
    • 2012-11-21
    • 2015-12-23
    • 2018-05-18
    相关资源
    最近更新 更多