问题 List抛出ConcurrentModificationException但是set不会抛出ConcurrentModificationException?


我有两个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的

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


4145
2018-02-26 14:43


起源



答案:


这是实现上的差异:数组列表返回的迭代器检测并发修改,即使它位于最后,因为它检查长度;的迭代器 HashSetTreeSet 和 LinkedList另一方面,不检测这种情况,因为它们在检查并发修改之前检查是否位于最后。该文档允许迭代器不要抛出并发修改,因此两种方法都是有效的。


4
2018-02-26 14:52



+1演示小提琴 - Rais Alam
nit:ArrayList迭代器实际检查修改计数,而不是长度本身,所以如果你添加然后立即删除一个元素,保持长度相同,之后你仍然会得到一个ConcurrentModificationException。 - Alice Purcell


答案:


这是实现上的差异:数组列表返回的迭代器检测并发修改,即使它位于最后,因为它检查长度;的迭代器 HashSetTreeSet 和 LinkedList另一方面,不检测这种情况,因为它们在检查并发修改之前检查是否位于最后。该文档允许迭代器不要抛出并发修改,因此两种方法都是有效的。


4
2018-02-26 14:52



+1演示小提琴 - Rais Alam
nit:ArrayList迭代器实际检查修改计数,而不是长度本身,所以如果你添加然后立即删除一个元素,保持长度相同,之后你仍然会得到一个ConcurrentModificationException。 - Alice Purcell


这是一种“逆行”行为,因为迭代器一旦完全遍历,就不能重复使用,也就是说 hasNext 到达列表末尾时,方法应返回false。

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

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

所以当你打电话 hasNext 在第二个循环中,它表示(错误地)有更多项要迭代,因为您在第一次迭代后执行了更改列表大小的操作。在语义上,您应该无法在到达结尾后继续迭代列表中的项目,但由于此实现细节,它允许您继续第二个while循环。当然,此时,由于您在后备列表中所做的更改,您将获得并发修改异常。

另一方面,哈希集使用的迭代器有它的 hasNext 实施如下:

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

这种实现不会像在迭代完成后对哈希集所做的修改那样“易受攻击”,因此也是如此 hasNext 方法表现得更好。


5
2018-02-26 14:49



好的,如果设置家庭? - Rais Alam
@Real - 添加了哈希集实现的详细信息。 - Perception


首先阅读 的JavaDoc 对于迭代器。它提到了吗? ConcurrentModificationException 地方?

现在,阅读JavaDoc for ConcurrentModificationException的,并注意以下内容(重点补充):

这个例外 可能 被检测到并发修改对象的方法抛出 何时不允许进行此类修改

现在仔细看看你的代码。你的 while 循环遍历集合的所有元素(即使第一个示例的输出未指示此情况,这告诉我您已编辑输出或这不是您的实际代码)。在删除元素时,没有其他项可以迭代,因此第二个循环应该总是立即退出。

因此,结论是列表迭代器的实现者具有 选择 即使在没有更多元素要迭代的情况下抛出该异常,而set迭代器的实现者也是如此 选择 不要。根据规格,这两种情况都完全可以接受。


2
2018-02-26 14:51



我在上面的代码中添加了相同的类,您可以运行代码并自己检查输出。 - Rais Alam
@Real - 是的,我运行它,输出显示“hibernate”,你的帖子没有。但实际上,你应该考虑一下这个问题,而不是让你的短片与括号中的一致 内容 我的回答。 - parsifal
知道为什么这个设计在哪个列表中抛出和设置不是首选? - djechlin
@djechlin - 这不是设计,而是实施。我最初评论说,这可能只是实施者的偏好,但我认为 知觉 有一个很好的解释(虽然我记得几年前我上次查看它们时实现的代码更多)。 - parsifal
但我的回答的关键点在于观察到的行为完全符合规范,因此没有充分理由对其提出质疑。并且它受到质疑的可能原因是OP取决于未指明的行为。 - parsifal


 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

1
2018-02-26 14:49



那么为什么设置不抛出异常。你可以检查两个班级 - Rais Alam
从输出堆栈跟踪中可以看出,当我们调用iterator next()函数时异常即将到来。如果您想知道Iterator如何检查修改,它的实现存在于AbstractList类中,其中定义了一个int变量modCount,它提供了列表大小已更改的次数。 - Biswajit


如果你通过迭代器对集合做任何事情,Hashset可以抛出ConcurrentModificationException。然而,围绕迭代器的快速失败行为有很多启发式方法,目标是尽可能完成迭代。 JavaDocs似乎很清楚它的行为。


1
2018-02-26 14:57





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

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

游标值和大小将变为相同.Cursor包含遍历的元素的总数和内部的hashNext()方法,用于列表遍历的代码包含以下代码:

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

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

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


0
2017-12-11 08:03