Java中的并发修改

时间:2014-07-17 06:04:14

标签: java

如果我写

String myFirstString = "a";
String mySecondString = "b";

List<String> lstOfStrings = new ArrayList<String>();
lstOfStrings.add(myFirstString);
lstOfStrings.add(mySecondString);

for (String value : lstOfStrings) {
    if(value.equals("a")) {
        lstOfStrings.remove("a");
        System.out.println("removed successfully");
    }
}

工作正常但是,

如果我改变列表中的插入顺序,它将给出java.util.ConcurrentModificationException,见下面的代码

String myFirstString = "a";
String mySecondString = "b";

List<String> lstOfStrings = new ArrayList<String>();
lstOfStrings.add(mySecondString);
lstOfStrings.add(myFirstString);

for (String value : lstOfStrings) {
    if(value.equals("a")) {
        lstOfStrings.remove("a");
        System.out.println("removed successfully");
    }
}

给出了java.util.ConcurrentModificationException

为什么会出现这种情况?我知道有很多方法像Ite​​rator,CoppyOnWriteArraylist一样解决ConcurrentModificationException Exception。但我想知道这个具体案件的原因。请解释一下。

5 个答案:

答案 0 :(得分:3)

Java for-each循环在内部使用集合的iterator

Java iterator的行为是fail-fast,当循环浏览集合中的任何内容时,它会失败。

有关详细信息,建议您阅读以下文章。

http://www.jguru.com/faq/view.jsp?EID=221988

http://www.developersfusion.com/Articles/AD/F/53/ConcurrentModificationException---Fail-Fast-and-Fail-Safe-iterators.aspx

http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html

答案 1 :(得分:3)

我猜想为for(val:collection)的迭代器语法糖生成的字节码的实现包括检查你是否在集合的最后一个元素而不打扰进入迭代器再次。因此,如果您删除集合中最后一个项目,它将不会抛出异常,只是跳过最后一项。 2个实验证明了这一点: 在第一个示例中添加第3个项目,它将失败。 修改它以删除第2项而不是第1项,并将完成&#34;成功&#34;试。

更新:嗯,这个问题是重复的 Why isn't this code causing a ConcurrentModificationException?

答案 2 :(得分:1)

这是因为ArrayList的迭代器的实现方式。

当你删除最后一个元素之前的元素时,迭代器将不再迭代最后一个元素。

尝试打印每次迭代

String myFirstString = "a";
String mySecondString ="b";
List<String> lstOfStrings = new ArrayList<String>();

lstOfStrings.add(myFirstString);
lstOfStrings.add(mySecondString);

for (String value : lstOfStrings) {
    System.out.println("Iterating: " + value);

    if(value.equals("a")){
        lstOfStrings.remove("a");
        System.out.println("removed successfully");
    }
}

System.out.println(lstOfStrings);

输出

Iterating: a
removed successfully
[b]

正如您在上一篇System.out.println中所看到的,lstOfString仍然包含元素“b”,但它没有被迭代。

当您查看ArrayList的实施时,您可以看到原因。

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

Iterator.hasNext()仅检查实际光标是否不是大小。因此,如果您遍历列表并删除字符串"a",则光标指向第二个元素"b",但大小已减少为1.因此第二个元素将不再被迭代。

即使在JDK 1.7中也会发生这种情况,我还没有测试1.8中是否仍存在此行为。

使用Iterator.remove()方法时,它会起作用,因为迭代器知道删除。

Iterator<String> iterator = lstOfStrings.iterator();
    while (iterator.hasNext()) {
        String value = iterator.next();
        System.out.println("Iterating: " + value);
        if (value.equals("a")) {
            iterator.remove();
            System.out.println("removed successfully");
        }
    }

打印出来:

Iterating: a
removed successfully
Iterating: b

答案 3 :(得分:1)

您的第一个代码段中没有异常,因为循环将在lstOfStrings.remove("a")之后和interator.next()调用之前爆发。 ArrayList的迭代器通过检查hasNext()检查return cursor != size,其中光标是迭代器中的当前位置,这意味着从列表中删除元素:a后,光标增加:cursor++(cursor==1),但大小减小(您删除了一个元素,size == 1)。它不会抛出异常,但并不意味着它是正确的。

请考虑以下代码:

    for (String value : lstOfStrings) {
    System.out.println("Iterating: " + value);
    if(value.equals("a")){
        lstOfStrings.remove("a");
        System.out.println("removed successfully");
    } else if (value.equals("b")) {
        doSomethingOnB(value) // this won't be executed anyway!!!
    }
}

如果您在第一个代码段中的lstOfStrings中再添加一个元素,例如:lstOfStrings.add("c"),它会抛出您想要的异常。 : - )

ConcurrentModificationException会在您的第二个代码段中抛出,因为它通过了hasNext()检查(cursor == 2,size == 1),然后进入interator.next() call,它将检查modCount中的ArrayList和Iterator中的expectedModCount,它表示对List元素大小的修改计数。显然,modeCount == 3(2次添加和1次移除),但ArrayList.remove(Object)方法不会影响expectedModCount中的ArrayList.Iterator,因此expectedModCount == 2,这就是为什么抛出异常。

我不确定它是否应该被视为ArrayList中的错误,你的情况中的根本原因是ArrayList的元素修改计数与Iterator的不同步。

答案 4 :(得分:0)

ArrayList API的一些摘录 -

  1. 每次添加和删除列表时,modCount都会递增。

  2. 对于每个循环的迭代器调用next()方法,列表大小+ 1次,在这种情况下为3次。

  3. 当lstOfStrings.remove(“a”);调用modCount设置为3。

  4. 迭代器checkForComodification()的每次next()迭代;被调用以检查是否有任何修改,因为发现修改我们得到ConcurrentModificationException。

  5. 为什么迭代器不会抛出异常呢?

    是否有调用checkForComodification();在此ArrayList.this.remove(lastRet)之后;它会引发同样的异常。