【问题标题】:How can i counter a ConcurrentModificationException?如何应对 ConcurrentModificationException?
【发布时间】:2012-06-19 05:48:27
【问题描述】:

如果有以下问题: 我有一个列表,我正在使用增强的 for 循环。每次我想从列表中删除某事时,我都会得到一个 ConcurrentModificationException。我已经知道为什么会抛出这个异常,但我不知道如何修改我的代码,以便它工作。这是我的代码:

for(Subject s : SerData.schedule)
    {
        //Checking of the class is already existing
        for(Classes c : s.classes)
        {
            if(c.day == day &c.which_class == which_class)
            {
                int index = getclassesindex(s.classes, new Classes(day, which_class));
                synchronized (s) {
                    s.classes.remove(index);

                }
            }
        }
            //More code....
    }

我也尝试了这个实现。

for(Subject s : SerData.schedule)
    {
        //Checking of the class is already existing
        Iterator<Classes> x = s.classes.iterator();
        while(x.hasNext())
        {
            Classes c = x.next();
            if(c.day == day &c.which_class == which_class)
            {
                int index = getclassesindex(s.classes, new Classes(day, which_class));
                synchronized (s) {
                    s.classes.remove(index);
                }
            }
        }
        //More code....
    }

也不行……

是否有常用的标准解决方案? (希望……这并不明显:D)

【问题讨论】:

  • 您是否错过了键入复制的代码,或者您的意思是在 if 语句(即单个 &)中按位和,我认为您应该有 &&
  • 我有 &&,打错字了...sry

标签: java for-loop concurrentmodification


【解决方案1】:

出现此问题的主要原因是您的 for-each 循环的语义。

当你使用 for-each 循环时,被遍历的数据结构不能被修改。

基本上任何这种形式的东西都会抛出这个异常:

for( Object o : objCollection )
{
    // ...
    if ( satisfiesSomeProperty ( o ) )
       objList.remove(o);    // This is an error!!
    // ...
}

附带说明,您也不能添加或替换集合中的元素。

有几种方法可以执行此操作。

一种方法是使用迭代器并在要删除对象时调用remove() 方法。

Iterator <Object> objItr = objCollection.iterator();

while(objItr.hasNext())
{
    Object o = objItr.next();
    // ...
    if ( satifiesSomeProperty ( o ) )
        objItr.remove();    // This is okay
    // ...
}

此选项的特性是对象的移除时间与迭代器的remove 方法成正比。

下一个选项是存储要删除的对象,然后在遍历列表后将其删除。这在迭代期间删除可能产生不一致结果的情况下可能很有用。

Collection <Object> objsToRemove = // ...
for( Object o : objCollection )
{
    // ...
    if ( satisfiesSomeProperty ( o ) )
       objsToRemove.add (o);
    // ...
}
objCollection.removeAll ( objsToRemove );

这两种方法适用于一般的Collection 类型,但对于列表,您可以使用标准的 for 循环并将列表从列表末尾移到前面,删除您喜欢的内容。

for (int i = objList.size() - 1; i >= 0; i--)
{
    Object o = objList.get(i);
    // ...
    if ( satisfiesSomeProperty(o) )
       objList.remove(i);
    // ...
}

也可以沿正常方向行走并移除,但您必须注意增量是如何发生的;具体来说,您不想在删除时增加 i,因为下一个元素会向下移动到相同的索引。

for (int i = 0; i < objList.size(); i++)
{
    Object o = objList.get(i);
    // ...
    if ( satisfiesSomeProperty(o) )
    {
       objList.remove(i);
       i--;
    }

    //caveat: only works if you don't use `i` later here
    // ...
}

希望这可以很好地概述这些概念并有所帮助!

【讨论】:

  • thx,该概述比谷歌的顶级结果更好:D 谢谢...但是,我认为迭代器的删除方法只是将其从迭代器中删除,而不是从集合中删除。我想我错了,但我不确定....是吗?
  • 抱歉没有尽快回复;为了帖子的完整性,我将回答您的第一个问题。迭代器的remove 方法的行为应该是从您正在迭代的底层数据结构中删除最后看到的对象
【解决方案2】:

使用Iterator.remove() 应该可以防止抛出异常。

【讨论】:

【解决方案3】:

嗯,如果我猜对了,你是在迭代一个类的集合,如果给定的类符合某些条件,你正在寻找它的索引并尝试删除它?

为什么不直接做:

Iterator<Classes> x = s.classes.iterator();
while(x.hasNext()){
    Classes c = x.next();
    if(c.day == day && c.which_class == which_class) {
        x.remove();
    }
}

如果需要,添加同步(但如果我是你,我更喜欢并发集合),最好将“==”更改为 equals(),添加 getter/setter 等。此外,java 中的约定是命名变量和使用 camelCase 的方法(而不是用“_”分隔它们)。

实际上,这是您必须使用迭代器的情况之一。

【讨论】:

    【解决方案4】:

    来自关于 ConcurrentModificationException 的 javadoc:

    “如果线程在使用快速失败迭代器迭代集合时直接修改了集合,则迭代器将抛出此异常。”

    所以在你的 for (类 c : s.classes)

    你正在执行 s.classes.remove(index)

    并且迭代器正在做它的合同所说的。在循环外的范围内声明索引,并在循环完成后删除您的目标。

    【讨论】:

      【解决方案5】:
      Iterator<Classes> classesIterator = s.classes.iterator();
      while (classesIterator.hasNext()) {
          Classes c = classesIterator.next();
          if (c.day == day && c.which_class == which_class) {
              classesIterator.remove();
          }
      }
      

      【讨论】:

        【解决方案6】:

        一般来说,Collection 子类没有通用解决方案 - 如果集合被修改,大多数迭代器将变得无效,除非修改通过迭代器本身通过 Iterator.remove() 发生。

        List 实现方面有一个潜在的解决方案:List 接口具有基于索引的add/get/set/remove 操作。无需使用Iterator 实例,您可以使用基于计数器的循环显式地遍历列表,就像使用数组一样。但是,您应该注意在插入或删除元素时适当地更新循环计数器。

        【讨论】:

          【解决方案7】:

          您的 for-each 迭代器是快速失败的,这就是删除操作失败的原因,因为它会在遍历集合时更改集合。

          您使用的是 List 接口的什么实现? 注意到主题上的同步,您是否同时使用此代码?

          如果是并发,那么我建议使用CopyOnWriteArrayList。它不需要同步,并且它的 for-each 迭代器不会抛出 ConcurrentModificationException。

          【讨论】:

            猜你喜欢
            • 2012-06-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-07-27
            • 2010-10-24
            • 2021-11-23
            相关资源
            最近更新 更多