迭代器应该在源修改后什么时候抛出异常?

在下面的代码片段中,为 ArrayList 创建了一个迭代器,之后 ArrayList 的结构发生了变化。然后使用迭代器。


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());

}

正如预期的那样, aConcurrentModificationException被抛出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。Iterator 认为 ArrayList 没有改变,因此不会抛出异常。


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


请注意,无法保证迭代器的快速失败行为,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬性保证。


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


这里发生了什么?第二个片段中的 Iterator 应该是这样的吗?如果是这样,那么 long 类型的 modCount 会不会好一点(问题仍然不会消失,但是 2^64 + 1 修改在合理的时间内执行是不可行的)。


这也可以在其他Collections 中观察到,它们使用相同的 int modCount 机制。


白猪掌柜的
浏览 145回答 3
3回答

缥缈止盈

Javadoc的下一句话:Fail-fast 迭代器ConcurrentModificationException在尽力而为的基础上抛出。因此,编写依赖于此异常的正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测错误。您只是很幸运(或者更确切地说,您正在构建一个非常精确的情况)您的代码不会抛出 CME。我不会过多解读前一句关于“非同步并发修改”的强调。对此的一种迂腐的解读是,因为一件事可能发生,而这打破了保证,整个保证就被打破了,所以保证可能被打破的其他方式并不重要。

ITMISS

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

潇湘沐

稍加思索后,我得出结论:如果 Iterator 抛出 a&nbsp;ConcurrentModificationException,则源在正在进行的迭代中进行了结构修改。如果源在其迭代器处于正在进行的迭代中时在结构上被修改,则该迭代器可能会或可能不会抛出ConcurrentModificationException.考虑到这一点,迭代器尝试抛出 a 的唯一原因ConcurrentModificationException是帮助调试。总比没有好,尤其是在一般情况下。在 modCount 中使用 long 而不是 int 将增加 may 的机会并减少may not在 2 中的机会。但只有使用可以容纳无限位数的类型才能使may有机会 1.0 和may没有0.0 的机会(当然至少在那种架构下)。因此,无论 modCount 使用多少个有限位,它都不会渐近地产生影响。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java