为什么这段代码不会导致ConcurrentModificationException? [重复]

问题描述 投票:9回答:4

这个问题在这里已有答案:

我正在阅读有关ConcurrentModificationException以及如何避免它的内容。找到an article。该文章中的第一个列表的代码类似于以下内容,这显然会导致异常:

List<String> myList = new ArrayList<String>();
myList.add("January");
myList.add("February");
myList.add("March");

Iterator<String> it = myList.iterator();
while(it.hasNext())
{
    String item = it.next();
    if("February".equals(item))
    {
        myList.remove(item);
    }
}

for (String item : myList)
{
    System.out.println(item);
}

然后它继续解释如何用各种建议解决问题。

当我试图重现它时,我没有得到例外!为什么我没有得到例外?

java iterator concurrentmodification
4个回答
8
投票

根据Java API文档,Iterator.hasNext不会抛出ConcurrentModificationException

检查"January""February"后,从列表中删除一个元素。调用it.hasNext()不会抛出ConcurrentModificationException但返回false。因此,您的代码干净利落地退出。但是从不检查最后一个String。如果将"April"添加到列表中,则会按预期获得异常。

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

public class Main {
        public static void main(String args[]) {

                List<String> myList = new ArrayList<String>();
                myList.add("January");
                myList.add("February");
                myList.add("March");
                myList.add("April");

                Iterator<String> it = myList.iterator();
                while(it.hasNext())
                {
                    String item = it.next();
                    System.out.println("Checking: " + item);
                    if("February".equals(item))
                    {
                        myList.remove(item);
                    }
                }

                for (String item : myList)
                {
                    System.out.println(item);
                }

        }
}

http://ideone.com/VKhHWN


4
投票

来自ArrayList来源(JDK 1.7):

private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

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

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

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

ArrayList上的每个修改操作都会增加modCount字段(自创建以来列表被修改的次数)。

创建迭代器时,它会将modCount的当前值存储到expectedModCount中。逻辑是:

  • 如果在迭代期间根本没有修改列表,modCount == expectedModCount
  • 如果列表由迭代器自己的remove()方法修改,modCount会增加,但expectedModCount也会增加,因此modCount == expectedModCount仍然有效
  • 如果一些其他方法(或甚至其他一些迭代器实例)修改列表,modCount会增加,因此modCount != expectedModCount,这导致ConcurrentModificationException

但是,正如您从源中看到的那样,检查不是在hasNext()方法中执行的,仅在next()中执行。 hasNext()方法也只将当前索引与列表大小进行比较。当您从列表中删除倒数第二个元素("February")时,这导致以下调用hasNext()只返回false并终止迭代,然后才能抛出CME。

但是,如果您删除了倒数第二个以外的任何元素,则会抛出异常。


1
投票

我认为正确的解释是来自ConcurrentModificationExcetion的javadocs的这个摘录:

请注意,无法保证快速失败的行为,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬性保证。失败快速操作会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:ConcurrentModificationException仅应用于检测错误。

因此,如果迭代器快速失败,它可能抛出异常,但不能保证。尝试在你的例子中用February替换January并抛出异常(至少在我的环境中)


0
投票

迭代器检查它已经迭代了多少次,因为在检查并发修改之前它已经到了结尾。这意味着如果仅删除第二个最后一个元素,则在同一个迭代器中看不到CME。

© www.soinside.com 2019 - 2024. All rights reserved.