【问题标题】:List throws ConcurrentModificationException but set does not throws ConcurrentModificationException? [duplicate]List 抛出 ConcurrentModificationException 但 set 不抛出 ConcurrentModificationException? [复制]
【发布时间】:2013-02-12 01:41:27
【问题描述】:

我有以下两个java类

import java.util.*;

public class ArrayListTest032 {
    public static void main(String[] ar) {
        List<String> list = new ArrayList<String>();
        list.add("core java");
        list.add("php");
        list.add("j2ee");
        list.add("struts");
        list.add("hibernate");

        Iterator<String> itr = list.iterator();

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        list.remove("php");

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }

    }
}

当我运行上面的代码时,我得到以下输出。

core java
php
j2ee
struts
hibernate

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at ArrayListTest032.main(ArrayListTest032.java:20)

这是预期的,因为我在迭代时修改列表。但在下面的 java 类中,相同的逻辑由 set family 执行。

import java.util.*;

public class HashSetTest021 {
    public static void main(String[] ar) {
        Set<String> set = new HashSet<String>();
        set.add("core java");
        set.add("php");
        set.add("j2ee");
        set.add("struts");
        set.add("hibernate");

        Iterator<String> itr = set.iterator();

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }
        set.remove("php");

        while (itr.hasNext()) {
            System.out.println(itr.next());
        }

    }
}

结果是。

hibernate
core java
j2ee
php
struts

没有任何 ConcurrentModificationException

我只想知道为什么同一段代码在list family 的情况下会抛出ConcurrentModificationException,但在set family 的情况下却没有任何ConcurrentModificationException

【问题讨论】:

    标签: java list arraylist set hashset


    【解决方案1】:
     public static void main(String[] ar) {
                List<String> list = new ArrayList<String>();
                list.add("core java");
                list.add("php");
                list.add("j2ee");
                list.add("struts");
                list.add("hibernate");
    
                Iterator<String> itr = list.iterator();
    
                while (itr.hasNext()) {
                    System.out.println(itr.next());
                }
                list.remove("php");
    
              /*  while (itr.hasNext()) {
                    System.out.println(itr.next());
                }*/
    
            }
    
    problem in itr object.it holds the list object reference
    

    【讨论】:

    • 那么为什么设置不抛出异常。你可以检查两个类
    • 从输出堆栈跟踪中可以清楚地看出,当我们调用迭代器 next() 函数时异常即将到来。如果您想知道 Iterator 如何检查修改,它的实现存在于 AbstractList 类中,其中定义了一个 int 变量 modCount,该变量提供列表大小已更改的次数。
    【解决方案2】:

    这是一种“逆行”行为,因为迭代器一旦被完全遍历就不可重用,也就是当您到达列表末尾时,它们的 hasNext 方法应该返回 false。

    但在这种情况下,ArrayList.iterator 返回的迭代器是一个内部实现类,hasNext 的代码如下:

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

    因此,当您在第二个循环中调用 hasNext 时,它(错误地)表明有更多的项目需要迭代,因为您在第一次迭代之后执行了更改列表大小的操作。从语义上讲,您应该无法在到达列表末尾后继续迭代列表中的项目,但是由于这个实现细节,它可以让您继续进行第二个 while 循环。当然,此时,由于您在后备列表中所做的更改,您会遇到并发修改异常。

    另一方面,您的哈希集使用的迭代器的hasNext 实现如下:

    public final boolean hasNext() {
        return next != null;
    }
    

    此实现恰好不会像在迭代完成后对哈希集所做的修改那样“脆弱”,因此hasNext 方法的表现更好。

    【讨论】:

    • 好的,如果设置家庭?
    • @Real - 添加了哈希集实现的细节。
    【解决方案3】:

    首先阅读 JavaDoc for Iterator。它是否在任何地方提及ConcurrentModificationException

    现在,阅读 ConcurrentModificationException 的 JavaDoc,并注意以下内容(已添加重点):

    此异常可能由检测到对象的并发修改的方法在不允许此类修改时抛出。

    现在仔细查看您的代码。您的 while 循环遍历集合的所有元素(即使您的第一个示例的输出没有表明这一点,这告诉我您已经编辑了输出或者这不是您的实际代码)。在您移除元素时,没有更多要迭代的项目,因此第二个循环应始终立即退出。

    因此,结论是列表迭代器的实现者选择了,即使没有更多元素要迭代,也会抛出异常,而集合迭代器的实现者有选择不去。考虑到规范,这两种情况都是完全可以接受的。

    【讨论】:

    • 我在上面的代码中添加了相同的类,你可以自己运行代码并检查输出。
    • @Real - 是的,我运行了它,输出显示“休眠”,而您的帖子没有。但实际上,与其让你的短裤因括号评论而扭曲,你应该真正考虑一下我的回答的内容
    • 知道为什么不喜欢这种列表抛出和设置的设计吗?
    • @djechlin - 这不是设计,而是实施。我最初评论说这可能只是实现者的偏好,但我认为 Perception 有一个很好的解释(尽管我记得几年前我上次查看实现时的更多代码)。
    • 但我回答的关键点是观察到的行为完全符合规范,因此没有充分的理由质疑它。它受到质疑的可能原因是 OP 依赖于未指定的行为。
    【解决方案4】:

    这是实现上的区别:数组列表返回的迭代器即使位于末尾也会检测并发修改,因为它检查长度;另一方面,HashSetTreeSetLinkedList 的迭代器不会检测到这种情况,因为它们会在检查并发修改之前检查是否位于末尾。该文档允许迭代器不要进行并发修改,因此这两种方法都有效。

    【讨论】:

    • nit:ArrayList 迭代器实际上检查的是修改计数,而不是长度本身,所以如果你添加然后立即删除一个元素,保持长度不变,之后你仍然会得到一个 ConcurrentModificationException。
    【解决方案5】:

    如果您对集合执行任何操作(除了通过迭代器),Hashset 可能会引发 ConcurrentModificationException。然而,围绕迭代器的快速失败行为有很多启发式方法,目标是尽可能完成迭代。 JavaDocs 似乎很清楚它的行为。

    【讨论】:

      【解决方案6】:

      如果是列表,当我们用第一个循环遍历它时 迭代器itr = set.iterator();

          while (itr.hasNext()) {
              System.out.println(itr.next());
          }
      

      游标值和大小将变得相同。游标包含遍历元素总数的值,并且列表遍历的 hashNext() 方法内部包含代码为:

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

      所以在第一个while循环游标==大小之后。但是从列表中删除元素后大小变成(originalSize-1)。所以对于下一个while循环,它进入while和itr.next()方法内部,它检查modcount修改和抛出 ConcurrentModificationException。

      在 Set 的情况下,它会为每个 itr.hasnext() 调用检查下一个!= null。并且在遍历第一个 while 循环后,下一个变为 null。从 set 中删除元素不会影响下一个值为 null 并且 itr.hasNext 将返回next == null 为真,因此它不会进入 while 循环来检查 modcount 修改。因此它不会抛出 ConcurrentModification 异常。

      【讨论】:

        猜你喜欢
        • 2013-08-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-24
        • 1970-01-01
        • 1970-01-01
        • 2014-09-11
        相关资源
        最近更新 更多