为啥在此示例中我没有收到 java.util.ConcurrentModificationException?

Posted

技术标签:

【中文标题】为啥在此示例中我没有收到 java.util.ConcurrentModificationException?【英文标题】:Why am I not getting a java.util.ConcurrentModificationException in this example?为什么在此示例中我没有收到 java.util.ConcurrentModificationException? 【发布时间】:2012-01-01 15:11:24 【问题描述】:

注意:我知道Iterator#remove() 方法。

在下面的代码示例中,我不明白为什么main 方法中的List.remove 抛出ConcurrentModificationException,但remove 方法中不是

public class RemoveListElementDemo     
    private static final List<Integer> integerList;

    static 
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    

    public static void remove(Integer toRemove) 
        for(Integer integer : integerList) 
            if(integer.equals(toRemove))                 
                integerList.remove(integer);
            
        
    

    public static void main(String... args)                 
        remove(Integer.valueOf(2));

        Integer toRemove = Integer.valueOf(3);
        for(Integer integer : integerList) 
            if(integer.equals(toRemove))                 
                integerList.remove(integer);
            
        
    

【问题讨论】:

在迭代列表时从列表中删除元素的唯一安全方法是使用Iterator#remove()。你为什么要这样做? @MattBall:我只是想看看这里可能是什么原因。因为,这两种方法中的“增强型 for 循环”是相同的,但一种抛出 ConcurrentModificationException 而另一种则没有。 您删除的元素有所不同。在该方法中,您删除了“中间元素”。主要是删除最后一个。如果您交换数字,您的方法中会出现异常。仍然不确定为什么会这样。 我遇到了类似的问题,当我的循环迭代了一个在我删除循环中的项目后不存在的位置时。我只是通过在循环中添加return; 来解决这个问题。 在 java8 android 上,删除除最后一个以外的元素会调用 ConcurrentModificationException。所以对于你的情况,删除函数会得到一个与你之前观察到的相反的异常。 【参考方案1】:

原因如下: 正如 Javadoc 中所说:

这个类的迭代器和listIterator返回的迭代器 方法是快速失败的:如果列表在任何结构上被修改 迭代器创建后的时间,除了通过 迭代器自己的删除或添加方法,迭代器会抛出一个 ConcurrentModificationException。

这项检查是在迭代器的next() 方法中完成的(正如您在堆栈跟踪中看到的那样)。但是只有当hasNext() 传递为真时,我们才会到达next() 方法,这是由 for each 调用以检查是否满足边界的方法。在你的 remove 方法中,当hasNext() 检查它是否需要返回另一个元素时,它会看到它返回了两个元素,现在删除一个元素后,列表只包含两个元素。所以一切都很顺利,我们完成了迭代。不会发生并发修改检查,因为这是在从未调用过的 next() 方法中完成的。

接下来我们进入第二个循环。在我们删除第二个数字后,hasNext 方法将再次检查是否可以返回更多值。它已经返回了两个值,但列表现在只包含一个。但是这里的代码是:

public boolean hasNext() 
        return cursor != size();

1 != 2,所以我们继续使用 next() 方法,它现在意识到有人在弄乱列表并触发异常。

希望能解决您的问题。

总结

List.remove() 从列表中删除倒数第二个元素时不会抛出 ConcurrentModificationException

【讨论】:

@pushy:只回答似乎回答了问题的实际问题,并且解释得很好。我接受这个答案,也+1。谢谢。【参考方案2】:

处理它的一种方法是从Collection(不是集合本身)的副本中删除某些内容(如果适用)。 Clone原始收藏它通过Constructor制作副本。

检测到并发的方法可能会抛出此异常 在不允许修改时修改对象。

对于您的具体情况,首先,考虑到您打算修改列表过去的声明,我认为final 不是一种方法

private static final List<Integer> integerList;

还可以考虑修改副本而不是原始列表。

List<Integer> copy = new ArrayList<Integer>(integerList);

for(Integer integer : integerList) 
    if(integer.equals(remove))                 
        copy.remove(integer);
    

【讨论】:

【参考方案3】:

前向/迭代器方法在删除项目时不起作用。您可以删除元素而不会出错,但是当您尝试访问已删除的项目时会出现运行时错误。您不能使用迭代器,因为 pushy 表明它会导致 ConcurrentModificationException,因此请改用常规 for 循环,但要后退一步。

List<Integer> integerList;
integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

int size= integerList.size();

//Item to remove
Integer remove = Integer.valueOf(3);

解决方案:

如果要删除列表元素,则以相反的顺序遍历数组。只需向后浏览列表,您就可以避免访问已删除的项目,从而消除异常。

//To remove items from the list, start from the end and go backwards through the arrayList
//This way if we remove one from the beginning as we go through, then we will avoid getting a runtime error
//for java.lang.IndexOutOfBoundsException or java.util.ConcurrentModificationException as when we used the iterator
for (int i=size-1; i> -1; i--) 
    if (integerList.get(i).equals(remove) ) 
        integerList.remove(i);
    

【讨论】:

好主意!【参考方案4】:

这个 sn-p 将总是抛出 ConcurrentModificationException。

规则是“在使用迭代器迭代它时,你不能修改(从列表中添加或删除元素)(当你使用 for-each 循环时会发生这种情况)”。

JavaDocs:

这个类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候列表在结构上被修改,除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器将抛出 ConcurrentModificationException。

因此,如果您想修改列表(或一般的任何集合),请使用迭代器,因为它会知道修改,因此将正确处理这些修改。

希望这会有所帮助。

【讨论】:

OP 明确指出其中一个循环不会引发异常,而疑问是发生这种情况的原因。 你所说的“疑问”是什么意思?【参考方案5】:

我遇到了同样的问题,但如果我将 en 元素添加到迭代列表中。 我是这样弄的

public static void remove(Integer remove) 
    for(int i=0; i<integerList.size(); i++) 
        //here is maybe fine to deal with integerList.get(i)==null
        if(integerList.get(i).equals(remove))                 
            integerList.remove(i);
        
    

现在一切正常,因为您没有在列表上创建任何迭代器,而是“手动”迭代它。并且条件i &lt; integerList.size() 永远不会欺骗您,因为当您将某些内容删除/添加到列表减量/增量的列表大小时..

希望对我有帮助,这就是解决方案。

【讨论】:

这不是真的!证据:运行此 sn-p 以查看结果: public static void main(String... args) List listOfBooks = new ArrayList(); listOfBooks.add("代码完成"); listOfBooks.add("代码 22"); listOfBooks.add("22 有效"); listOfBooks.add("Netbeans 33"); System.err.println("删除前:" + listOfBooks); for (int index = 0; index 【参考方案6】:

如果您使用写时复制集合,它将起作用;但是,当您使用 list.iterator() 时,返回的 Iterator 将始终引用元素的集合,就像它在(如下所示)时一样 list.iterator() 被调用,即使另一个线程修改了集合。任何 在基于写时复制的迭代器或 ListIterator 上调用的变异方法 (如添加、设置或删除)将抛出 UnsupportedOperationException。

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class RemoveListElementDemo     
    private static final List<Integer> integerList;

    static 
        integerList = new CopyOnWriteArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    

    public static void remove(Integer remove) 
        for(Integer integer : integerList) 
            if(integer.equals(remove))                 
                integerList.remove(integer);
            
        
    

    public static void main(String... args)                 
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) 
            if(integer.equals(remove))                 
                integerList.remove(integer);
            
        
    

【讨论】:

【参考方案7】:

这在 Java 1.6 上运行良好

~ % javac RemoveListElementDemo.java ~ % java RemoveListElementDemo ~ % cat RemoveListElementDemo.java

import java.util.*;
public class RemoveListElementDemo     
    private static final List<Integer> integerList;

    static 
        integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
    

    public static void remove(Integer remove) 
        for(Integer integer : integerList) 
            if(integer.equals(remove))                 
                integerList.remove(integer);
            
        
    

    public static void main(String... args)                 
        remove(Integer.valueOf(2));

        Integer remove = Integer.valueOf(3);
        for(Integer integer : integerList) 
            if(integer.equals(remove))                 
                integerList.remove(integer);
            
        
    

~%

【讨论】:

抱歉打错了这在 Java 1.6 上运行良好 嗯...可能你有不同的实现。但根据规范,它应该这样做,IMO。看看@Pushy 的回答。 不幸的是,id 在 java 1.8 上没有【参考方案8】:

就我而言,我是这样做的:

int cursor = 0;
do 
    if (integer.equals(remove))
        integerList.remove(cursor);
    else cursor++;
 while (cursor != integerList.size());

【讨论】:

【参考方案9】:

将迭代器for each改为for loop即可解决。

原因是:

这个类的迭代器和listIterator返回的迭代器 方法是快速失败的:如果列表在任何结构上被修改 迭代器创建后的时间,除了通过 迭代器自己的删除或添加方法,迭代器会抛出一个 ConcurrentModificationException。

--参考的 Java 文档。

【讨论】:

【参考方案10】:

检查你的代码人......

在 main 方法中,您试图删除不存在的第 4 个元素,因此会出现错误。 在 remove() 方法中,您尝试删除存在的第三个元素,因此没有错误。

【讨论】:

你错了:数字23 不是列表的索引,而是元素。两个删除逻辑都针对列表元素检查equals,而不是元素的索引。此外,如果它与索引相关,它将是IndexOutOfBoundsException,而不是ConcurrentModificationException

以上是关于为啥在此示例中我没有收到 java.util.ConcurrentModificationException?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在这个 angular2 示例中我仍然需要 @inject?

为啥我在此 SQL Server 内联表值函数上收到 11555 错误?

为啥我会收到“错误:‘pthread_delay_np’未在此范围内声明”?

我有一些关于 jquery autocomplted 的示例。在控制器中我无法收到任何 empName

为啥在 iOS 7 中我的本地通知没有显示横幅

为啥在 vuejs2 中我的默认道具没有传递给我的子组件?