在以下代码段中,创建了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
中也可以看到这种情况。
答案 0 :(得分:2)
Javadoc的下一个句子:
快速迭代器会尽最大努力抛出
ConcurrentModificationException
。因此,编写依赖于此异常的程序以确保其正确性是错误的:迭代器的快速失败行为应仅用于检测错误。
您很幸运(或者,您正在构造一种非常精确的情况)您的代码没有引发CME。
我不会在前面的句子中强调“不同步的并发修改”。对这一点的粗俗读物是,因为可能发生一件事,并且破坏了保证,所以保证整体上被破坏了,所以不管如何破坏保证也没关系。
答案 1 :(得分:0)
您所做的所有事情都是创建一个场景,在该场景中,modCount最终会回绕,直到它处于某种状态,以反映未发生任何修改,因此没有CME。也许您希望modCount是一个long而不是一个int。
答案 2 :(得分:0)
再想一想,我得出结论:
ConcurrentModificationException
,则在进行中的迭代过程中对源进行了结构修改。ConcurrentModificationException
。考虑到这一点,迭代器尝试抛出ConcurrentModificationException
的唯一原因是帮助调试。总比没有好,特别是在一般情况下。
在modCount中使用long代替int将增加2中 may 的机会,并降低 may 的机会,但仅适用于可以容纳无限数量的数字可以使可能的机会为1.0,而可能的机会为0.0(当然,至少对于该体系结构而言)。因此,无论modCount使用多少个有限数字,它都不会渐近地起作用。