修改源代码后,Iterator应该何时引发异常?

时间:2019-06-06 13:45:36

标签: java arraylist iterator

在以下代码段中,创建了ArrayList的迭代器,然后ArrayList在结构上发生了变化。然后使用Iterator。

ArrayList<Integer> list = new ArrayList<>(2);
list.add(1);

Iterator<Integer> itr = list.iterator();
list.clear();
list.add(2);
list.add(3);

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

如预期的那样,在ConcurrentModificationException处抛出了itr.next()

现在,看看这个:

ArrayList<Integer> list = new ArrayList<>(2);
list.add(1);
int modCount = 1;

Iterator<Integer> itr = list.iterator();
do {
    list.clear();
    list.add(2);
    list.add(3);
    modCount += 3;
} while (modCount != 1);

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

几秒钟后,执行将毫无例外地结束,结果是:

2
3

ArrayList在结构上发生了变化,其最终状态与第一个代码段相同,但是即使在第一个代码段中也没有抛出异常。

在查看ArrayList源代码之后,这是可以预期的,因为基础的modCount的类型为int。对ArrayList进行足够多的修改会导致modCount溢出,在某个时刻返回到1。迭代器认为ArrayList保持不变,并且不会引发异常。

在Java SE 12文档中,对于ArrayList类,声明为:

  

请注意,不能保证迭代器的快速失败行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。

很明显,迭代器可能会犯“错误”,但这又是针对不同步的并发修改,对吗?但是,上面的第二个片段是同步的。

这是怎么回事?第二个片段中的Iterator是否应该具有这种行为?如果是这样,那么类型的modCount不会长久好一点(问题不会消失,但在合理的时间内执行2 ^ 64 +1修改是不可行的。)

在其他使用相同的int modCount机制的Collection中也可以看到这种情况。

3 个答案:

答案 0 :(得分:2)

Javadoc的下一个句子:

  

快速迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误

您很幸运(或者,您正在构造一种非常精确的情况)您的代码没有引发CME。

我不会在前面的句子中强调“不同步的并发修改”。对这一点的粗俗读物是,因为可能发生一件事,并且破坏了保证,所以保证整体上被破坏了,所以不管如何破坏保证也没关系。

答案 1 :(得分:0)

您所做的所有事情都是创建一个场景,在该场景中,modCount最终会回绕,直到它处于某种状态,以反映未发生任何修改,因此没有CME。也许您希望modCount是一个long而不是一个int。

答案 2 :(得分:0)

再想一想,我得出结论:

  1. 如果迭代器抛出ConcurrentModificationException,则在进行中的迭代过程中对源进行了结构修改
  2. 如果在迭代器正在进行迭代时对源进行了结构修改,则迭代器可能可能不会抛出ConcurrentModificationException
  3. li>

考虑到这一点,迭代器尝试抛出ConcurrentModificationException的唯一原因是帮助调试。总比没有好,特别是在一般情况下。

在modC​​ount中使用long代替int将增加2中 may 的机会,并降低 may 的机会,但仅适用于可以容纳无限数量的数字可以使可能的机会为1.0,而可能的机会为0.0(当然,至少对于该体系结构而言)。因此,无论modCount使用多少个有限数字,它都不会渐近地起作用。