奇怪的Java并发修改异常示例

时间:2018-10-07 13:56:36

标签: java exception

如果我们这样写,则存在并发修改异常:

public static void main(String... args) {
    List<String> listOfBooks = new ArrayList<>();
    listOfBooks.add("Programming Pearls");
    listOfBooks.add("Clean Code");
    listOfBooks.add("Effective Java");
    listOfBooks.add("Code Complete");

    System.err.println("Before deleting : " + listOfBooks);
    for (String book : listOfBooks) {
        if (book.contains("Code")) {
            listOfBooks.remove(book);
        }
    }
    System.err.println("After deleting : " + listOfBooks);
}

另一方面,如果我们这样写,则没有并发修改异常! 请注意,除了用于比较的字符串外,代码完全相同,在第一个示例中,它是 Code ,在第二个示例中,它是 Java

public static void main(String... args) {
    List<String> listOfBooks = new ArrayList<>();
    listOfBooks.add("Programming Pearls");
    listOfBooks.add("Clean Code");
    listOfBooks.add("Effective Java");
    listOfBooks.add("Code Complete");

    System.err.println("Before deleting : " + listOfBooks);
    for (String book : listOfBooks) {
        if (book.contains("Java")) {
            listOfBooks.remove(book);
        }
    }
    System.err.println("After deleting : " + listOfBooks);
}

我正在使用NetBeans 8.2,Windows 7 32位和JDK 1.8.0_131 怎么了?

3 个答案:

答案 0 :(得分:6)

List.remove()从列表中删除倒数第二个元素时不会抛出ConcurrentModificationException

引用Java Bug (JDK-4902078)

  

在将Collections Framework添加到平台后,检查一次共修饰是否昂贵,而不是每个迭代两次。检查是在Iterator.next而不是Iterator.hasNext上进行的。专家评审认为这已经足够。他们没有意识到它无法检测到一个重要情况:如果在迭代中最后一次调用hasNext之前立即从列表中删除了一个元素,则该调用将返回false,并且迭代将终止,而忽略了列表中的最后一个元素。

您也可以检查以下答案:-

https://stackoverflow.com/a/8189786/1992276

答案 1 :(得分:3)

有两种用于遍历集合的方法:枚举和迭代器。

第一个允许在迭代过程中修改集合(缓慢失败),第二个不允许(快速失败)。在for-each循环中,您使用的是迭代器,因此在迭代期间对集合进行的任何修改都会导致异常。

您有3种选择来解决此问题:

改为使用迭代器:

Iterator<String> bookIt = listOfBooks.iterator();
while(bookIt.hasNext()){
   String book = bookIt.next();
   if (book.contains("Java")) {
       bookIt.remove();
   }
}

使用仅可接受的元素创建新列表(过滤掉不需要的元素):

 List<String> booksWithNoCode =  listOfBooks.stream()
 .filter(book-> !book.contains("Code"))
 .collect(toList())

使用Collection.removeIf(),您将从列表中删除所有与给定条件匹配的元素。

listOfBooks.removeIf(book-> book.contains("Code"))

您可以在this posthere中找到更多信息。

答案 2 :(得分:2)

使用for每个循环对其进行迭代时,不能修改listOfBooks。


编辑:

for (String book : listOfBooks) {
    if (book.contains("Code")) {
        listOfBooks.remove(book);
    }
}

与:

    for (Iterator<String> i = listOfBooks.iterator(); i.hasNext();) {
        String book = i.next();
        if (book.contains("Code")) {
            listOfBooks.remove(book);
        }
    }

http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java

arraylist代码中的键是:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

和迭代器代码:

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

光标始终指向下一个元素,因此当您获得“有效Java”时,i = 2,但光标为3。

调用删除按钮时,光标位于3,大小为4。

然后通过remove减小大小,现在光标== size,下一个hasNext()返回false结束循环。