理解 concurrentModificationException 和 ArrayList 的实现

Posted

技术标签:

【中文标题】理解 concurrentModificationException 和 ArrayList 的实现【英文标题】:Understanding concurrentModificationException and implementation of ArrayList 【发布时间】:2016-04-10 07:52:26 【问题描述】:

我尝试通过编写以下代码自己重现 ConcurrentModificationException:

List<String> last = new ArrayList<>();
last.add("a");
last.add("b");
for(String i : last)
    System.out.println(i);
    last.remove(i);

System.out.println(last);

DEMO

因为documentation of ArrayList mentioned

请注意,无法保证迭代器的快速失败行为 因为一般来说,不可能做出任何硬性保证 在存在不同步的并发修改的情况下。

我希望在单线程程序中这样的检测是直截了当的。但是程序打印出来了

a
[b]

相反。为什么?

【问题讨论】:

Iterating through a list, avoiding ConcurrentModificationException when removing in loop的可能重复 【参考方案1】:

如文档中所述:

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

因为您正在使用新语法 for(String i : last) 循环遍历列表,所以会为您创建一个迭代器,并且 您无法在循环时修改列表

此异常与多线程无关。也可以只使用一个线程,您可以抛出该异常。

内部有一个变量modCount,每次修改列表结构时都会递增。首次创建迭代器时,它将modCount 的值保存在变量expectedModCount 中。每次后续修改检查expectedModCount 的值是否等于modCount。如果不是 ConcurrentModificationException 则被抛出。

我添加remove的代码作为例子。 addaddAll 和所有其他修改列表的方法也是如此。

public E remove(int index) 
    rangeCheck(index);

    // Check if a modification should thrown a ConcurrentModificationException
    checkForComodification();  

    E result = parent.remove(parentOffset + index);
    this.modCount = parent.modCount;
    this.size--;
    return result;


final void checkForComodification() 
    if (expectedModCount != ArrayList.this.modCount)
        throw new ConcurrentModificationException();
    

【讨论】:

您声明“为您创建了一个迭代器,您无法在循环时修改列表。”但这正是 St.Antario 所做的。他们能够在循环时从列表中删除一个元素。他们希望抛出异常,但没有抛出。他们想知道为什么。【参考方案2】:

您的代码相当于以下内容:

   List<String> last = new ArrayList<>();
   last.add("a");
   last.add("b");
   for(Iterator<String> i = last.iterator(); i.hasNext(); ) 
       String value = i.next();
       System.out.println(value);
       last.remove(value);
   
   System.out.println(last);

for循环的流程是:

 System.out.println(value); // prints "a"
 last.remove(value);        // removes "a" from the list
 i.hasNext()                // exits the loop, since i.hasNext() is false
 System.out.println(last);  // prints "[b]" - the updated list

这就是为什么你得到你的输出而不是ConcurrentModificationException。 如果您将另一个值添加到您的列表(例如last.add("c")),您将获得ConcurrentModificationException,因为在第一次迭代后i.hasNext() 将为真,i.next() 将抛出异常。

【讨论】:

是的,问题的关键在于 i.hasNext() 为假,因此不会发生对共同修改的检查,也不会抛出异常。它是错误的原因是因为就迭代器而言,它已经遍历了一个大小为 1 的列表,所以它完成了【参考方案3】: ArrayList 类中的

Itr 类有以下方法

public boolean hasNext() 
            return cursor != size;
        

 final void checkForComodification() 
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    

这里,modCount 是列表结构被修改的次数。当我们创建 for 循环时,会在内部创建一个迭代器,并将 expectedModCount 初始化为 modCount

当列表中只有 2 个元素并且删除一个元素后,for 循环 将使用 hasNext() 方法调用检查条件。因此,条件 cursor != size (1!=1) 将首先得到满足。因此,循环不会继续进行,也不会抛出 ConcurrentModificationException。

但是,当列表中有 1,3,4 等数量的元素时,for 循环 将在 hasNext() 方法调用之后继续进行。但是,在 for 循环 中使用 next() 方法获取元素时,它会调用 checkForComodification() 和条件 modCount !=将满足预期的ModCount。因此会抛出异常。

【讨论】:

这是对flow的清晰详细的解释。谢谢! 谢谢@Leonli :)【参考方案4】:

对于两个值,它不会失败,因为会退出 for 循环。添加第三个元素,你会得到 java.util.ConcurrentModificationException

List<String> last = new ArrayList<>();
last.add("a");
last.add("b");
last.add("c");
for(String i : last)
    System.out.println(i);
    last.remove(i);

System.out.println(last);

【讨论】:

以上是关于理解 concurrentModificationException 和 ArrayList 的实现的主要内容,如果未能解决你的问题,请参考以下文章

2021年Java集合面试Top问题 - 第二部分

Java 快速失败( fail-fast ) 安全失败( fail-safe )

烦躁 当写日记了

python3 变量理解 解释器理解 常量理解 用户交互理解

深入理解spring

如何理解FFT