9

我正在阅读有关 ConcurrentModificationException 以及如何避免它的信息。找到一篇文章。该文章中的第一个清单具有类似于以下的代码,这显然会导致异常:

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);
}

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

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

4

4 回答 4

8

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

检查后"January""February"您从列表中删除一个元素。调用it.hasNext()不会抛出 aConcurrentModificationException但返回 false。因此,您的代码干净地退出。然而,最后一个字符串永远不会被检查。如果您添加"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

于 2013-02-03T14:52:55.203 回答
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();
    }
}

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

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

  • 如果列表在迭代过程中根本没有被修改,modCount == expectedModCount
  • 如果列表被迭代器自己的remove()方法修改,modCount则增加,但也expectedModCount增加,因此modCount == expectedModCount仍然成立
  • 如果其他方法(甚至其他迭代器实例)修改列表,则modCount增加,因此modCount != expectedModCount,这会导致ConcurrentModificationException

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

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

于 2013-02-03T15:39:06.027 回答
1

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

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

因此,如果迭代器快速失败,它可能会抛出异常,但不能保证。尝试在您的示例中替换FebruaryJanuary并引发异常(至少在我的环境中)

于 2013-02-03T14:53:16.747 回答
0

迭代器在检查并发修改之前检查它是否已经迭代了尽可能多的元素以查看它是否已到达末尾。这意味着如果您仅删除倒数第二个元素,您将不会在同一个迭代器中看到 CME。

于 2013-02-03T20:37:37.000 回答