java.util.List的异常行为基于其中的元素数量

Abnormal behaviour of java.util.List based on number of elements in it

本问题已经有最佳答案,请猛点这里访问。

我知道,如果在某个线程使用迭代器遍历集合时更改该集合,则迭代器.next()将抛出ConcurrentModificationException。

但它根据列表中元素的数量显示不同的行为。

我尝试了一个代码片段,在该代码片段中,我遍历了每个循环的一个列表,并在其中遍历,使用该列表的remove()方法从列表中删除了一个元素。

理想情况下,它应该在这种情况下抛出一个ConcurrentModificationException,而不依赖于列表中的元素数,但是当列表中的元素数为2时,它不是真的。

案例1:列表-1中的元素数

1
2
3
4
5
6
7
8
9
10
11
 public static void main(String[] args)
    {
        List<String> list=new ArrayList<String>();
        list.add("One");

        for (String string : list)
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

Exception in thread"main" java.util.ConcurrentModificationException

这是意料之中的。

案例2:列表2中的元素数

1
2
3
4
5
6
7
8
9
10
11
12
 public static void main(String[] args)
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("two");

        for (String string : list)
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

没有引发异常?????????????

案例3:列表-3中的元素数

1
2
3
4
5
6
7
8
9
10
11
12
13
 public static void main(String[] args)
    {
        List<String> list=new ArrayList<String>();
        list.add("One");
        list.add("Two");
        list.add("Three");

        for (String string : list)
        {
            System.out.println(string);
            list.remove(string);
        }
    }

Output: One

Exception in thread"main" java.util.ConcurrentModificationException

再次抛出一个异常,这是理想的行为。

但是为什么它在case-2中正常运行而不抛出任何ConcurrentModificationException。


之前发布的答案向您展示了相关的文档,这些文档解释了为什么这是适当的行为;您不能保证收到该异常。

如果您真的很想知道为什么不使用两个元素来接收它(或者,如果不考虑大小而删除next-to-last元素),那么可以查看ArrayList的源代码。

你的循环:

1
for (String string : list)

实际上是:

1
2
3
4
for(Iterator<String> i = list.iterator(); i.hasNext(); ) {
    String string = i.next();
    ...
}

在arraylist内部有一个int size,它表示ArrayList中的当前元素数。当您调用remove()时,它会递减。

Iterator内有一个int cursor代表Iterator的当前位置(索引)。当您调用next()时,它将递增。

Iterator中的hasNext()根据大小检查当前光标位置。

在您的示例中,事件链如下:

  • cursor0开始,size2开始。
  • 调用next()cursor递增到1
  • 称为remove(),将size减为1
  • hasNext()cursorsize进行比较,发现两者相同,返回false
  • 循环退出而不引发异常

因此,如果在迭代过程中删除任何大小的ArrayList中的next-to-last元素,则不会收到异常。(同样值得注意的是,您永远不会处理循环中该列表中的最后一个元素;您的示例仅打印One,因为这个原因)。

不过,请记住-这是一个实现细节,不能保证。文档告诉您,您不应该依赖被抛出(或未抛出)的异常。如果上述内容不再适用并且抛出了异常(实际上,在另一个可能已经存在的JVM中),则可以用不同的方式重写ArrayList


从文档(强调我的):

The iterators returned by this class's iterator and listIterator
methods are fail-fast: if the list is structurally modified at any
time after the iterator is created, in any way except through the
iterator's own remove or add methods, the iterator will throw a
ConcurrentModificationException. Thus, in the face of concurrent
modification, the iterator fails quickly and cleanly, rather than
risking arbitrary, non-deterministic behavior at an undetermined time
in the future.

Note that the fail-fast behavior of an iterator cannot be guaranteed
as it is, generally speaking, impossible to make any hard guarantees
in the presence of unsynchronized concurrent modification. Fail-fast
iterators throw ConcurrentModificationException on a best-effort
basis. Therefore, it would be wrong to write a program that depended
on this exception for its correctness: the fail-fast behavior of
iterators should be used only to detect bugs.


从列表中删除指定元素时,列表似乎不知道其大小已更改。

试试这个:

迭代器.remove()

Removes from the underlying collection the last element returned by
the iterator (optional operation). This method can be called only once
per call to next. The behavior of an iterator is unspecified if the
underlying collection is modified while the iteration is in progress
in any way other than by calling this method.