理解 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的代码作为例子。 add
、addAll
和所有其他修改列表的方法也是如此。
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 的实现的主要内容,如果未能解决你的问题,请参考以下文章
Java 快速失败( fail-fast ) 安全失败( fail-safe )