【问题标题】:Weird Java Concurrent modification exception example [duplicate]奇怪的Java并发修改异常示例[重复]
【发布时间】:2019-03-12 08:27:52
【问题描述】:

这样写的话,有并发修改异常:

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 怎么了?

【问题讨论】:

    标签: java exception


    【解决方案1】:

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

    引用此Java Bug (JDK-4902078)

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

    您也可以查看这个答案:-

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

    【讨论】:

    • 我的结论是这个错误 Java Bug (JDK-4902078) 仍然存在。我只是添加了更多的“add()”行,CM 异常只发生在不等于 before-last-element 的元素上。如果我正确理解了这个错误状态,这是 已关闭且不会修复 任务吗?这意味着该错误将继续存在,对吗?
    • @dobrivoje 首先,这不是一个错误。错误是一些文档说它以某种方式做了某事,但实际上并没有。据说ConcurrentModificationException 是在一些尽力而为 上抛出的,而不是总是。由于这确实是一个边缘问题,不会有很多人滥用它,因此可能会保持这种状态
    • 我想知道他们是如何确定这个问题“已经证明可能对现有代码产生重大的兼容性影响”(错误报告中的不会修复原因)。这只是工程师在这种情况下通常使用的通常的挥手论证,还是他们实际上在现有代码中有一个或多个示例,他们尝试并确定“利用”这个问题(当然是不经意间) ? (P.S.:我并不是在贬低那种手摇的论点——在这种情况下,我有时会自己使用它——我只是好奇。)
    【解决方案2】:

    有两种方法可以迭代集合:枚举和迭代器。

    第一个允许在迭代期间修改集合(失败慢),第二个不允许(快速失败)。在 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 中找到更多信息。

    【讨论】:

    • 我同意您的建议,我知道迭代器解决方案是最正确的解决方案以及流解决方案(因为它是不可修改的),但这不是我的问题。我希望在这两种情况下都会出现 CM 异常,并且我只在“代码”的情况下得到它,而不是在“Java”的情况下得到它
    【解决方案3】:

    在使用 for each 循环进行迭代时,您无法修改 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代码中的key是:

    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。

    当您调用 remove 时,光标位于 3,大小为 4。

    然后大小由 remove 递减,现在 cursor == size 并且下一个 hasNext() 返回 false 结束循环。

    【讨论】:

    • 没错,但是,唯一的区别是数据主题本身,所以我希望在这两种情况下都能看到异常,你不同意吗?
    • 嗯,这很有趣...看来您只能安全地删除倒数第二个元素...我会深入研究一下,看看是否找到了什么:)
    猜你喜欢
    • 2012-10-29
    • 1970-01-01
    • 1970-01-01
    • 2014-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多