为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

白猪掌柜的 2023-01-05 16:48:01

在下面的代码片段中,为 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 机制。


查看完整描述

3 回答

?
缥缈止盈

TA贡献1780条经验 获得超2个赞

Javadoc的下一句话:

Fail-fast 迭代器ConcurrentModificationException在尽力而为的基础上抛出。因此,编写依赖于此异常的正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测错误

您只是很幸运(或者更确切地说,您正在构建一个非常精确的情况)您的代码不会抛出 CME。

我不会过多解读前一句关于“非同步并发修改”的强调。对此的一种迂腐的解读是,因为一件事可能发生,而这打破了保证,整个保证就被打破了,所以保证可能被打破的其他方式并不重要。


查看完整回答
反对 回复 2023-01-05
?
ITMISS

TA贡献1577条经验 获得超8个赞

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



查看完整回答
反对 回复 2023-01-05
?
潇湘沐

TA贡献1561条经验 获得超6个赞

稍加思索后,我得出结论:

  1. 如果 Iterator 抛出 a ConcurrentModificationException,则源在正在进行的迭代中进行了结构修改。

  2. 如果源在其迭代器处于正在进行的迭代中时在结构上被修改,则该迭代器可能会可能不会抛出ConcurrentModificationException.

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

在 modCount 中使用 long 而不是 int 将增加 may 的机会减少may not在 2 中的机会。但只有使用可以容纳无限位数的类型才能使may有机会 1.0 和may没有0.0 的机会(当然至少在那种架构下)。因此,无论 modCount 使用多少个有限位,它都不会渐近地产生影响。


查看完整回答
反对 回复 2023-01-05

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信