当前位置: 首页 > 面试题库 >

为什么此代码不引发ConcurrentModificationException?

公孙阳羽
2023-03-14
问题内容

为什么这段代码不抛出ConcurrentModificationException?它Collection在不使用Iterator.remove()方法的情况下修改了一段时间,这是唯一安全的删除方法。

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
for (String string : strings)
    if ("B".equals(string))
        strings.remove("B");
System.out.println(strings);

如果将替换为ArrayList,则会得到相同的结果LinkedList。但是,如果我将列表更改为("A", "B", "C", "D)或只是("A", "B")得到了预期的异常。到底是怎么回事?我正在使用jdk1.8.0_25是否相关。

编辑

我找到了以下链接

http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4902078

相关部分是

天真的解决方案是将协同修改检查添加到AbstractList的hasNext中,但这会使协同修改检查的成本加倍。事实证明,仅在最后一次迭代上进行测试就足够了,这几乎没有增加任何成本。换句话说,hasNext的当前实现:

    public boolean hasNext() {
        return nextIndex() < size;
    }

替换为以下实现:

    public boolean hasNext() {
        if (cursor != size())
            return true;
        checkForComodification();
        return false;
    }

由于Sun内部监管机构拒绝了此更改,因此不会进行此更改。正式裁定表明,更改“表明了对现有代码产生重大兼容性影响的潜力”。(“兼容性影响”是该修复程序有可能用ConcurrentModificationException替换无声行为)。


问题答案:

通常,ConcurrentModificationException检测到 修改时引发s ,而不 引起
。如果修改后从未访问过迭代器,则不会引发异常。ConcurrentModificationException不幸的是,这一微小的细节使它对于检测数据结构的滥用非常不可靠,因为它们仅在损坏完成后才抛出。

这种情况不会抛出a,ConcurrentModificationException因为next()修改后不会在创建的迭代器上调用它。

For-each循环实际上是迭代器,因此您的代码实际上如下所示:

List<String> strings = new ArrayList<>(Arrays.asList("A", "B", "C"));
Iterator<String> iter = strings.iterator();
while(iter.hasNext()){
    String string = iter.next();
    if ("B".equals(string))
        strings.remove("B");
}
System.out.println(strings);

考虑您的代码在您提供的列表上运行。迭代看起来像:

  1. hasNext() 返回true,进入循环,-> iter移至索引0,字符串=“ A”,未删除
  2. hasNext()返回true,继续循环-> iter移至索引1,删除字符串=“ B”。strings现在长度为2。
  3. hasNext() 返回false(iter当前位于最后一个索引,没有更多的索引可用),退出循环。

因此,ConcurrentModificationException当调用next()检测到进行了修改时抛出s时,此方案几乎避免了此类异常。

对于您的其他两个结果,我们确实有例外。对于"A", "B", "C", "D",删除“
B”之后,我们仍然处于循环中,并next()检测到ConcurrentModificationException,而对于"A", "B"我来说,它是某种ArrayIndexOutOfBounds被捕获并重新抛出为ConcurrentModificationException



 类似资料:
  • 这似乎与对象没有被实例化有关,尽管我不太明白为什么。有人知道出什么事了吗?

  • 问题内容: var x int done := false go func() { x = f(…); done = true } while done == false { } 这是Go代码。我的恶魔告诉我,这是UB代码。为什么? 问题答案: Go Memory Model不保证该程序将始终遵守在goroutine中写入x的值。go常规销毁 部分中提供了一个类似的错误程序作为示例。 在本节中,G

  • 我正在做一个编码练习:给定一个整数序列作为一个数组,确定是否可以通过从数组中删除不超过一个元素来获得严格递增的序列。 所以我写了这段代码: 现在,这段代码似乎适用于大多数序列,但这段代码引发了一个错误: 错误如下: 我只是不明白列表索引怎么可能超出范围…有人有线索吗?

  • 问题内容: 我编写了以下程序: 由于通道事件列表是一个缓冲通道,我想我应该获得100倍的输出“嘿!”,但是它只显示一次。我的错误在哪里? 问题答案: 更新(Go 1.2版或更高版本) 从Go 1.2开始,调度程序基于 抢先式多任务处理 原则。这意味着原始问题(以及下面提供的解决方案)中的问题不再相关。 从Go 1.2发行说明中 调度程序中的抢占 在以前的版本中,永远循环的goroutine可能会使

  • 问题内容: 在方法或类范围内,下面的行进行编译(带有警告): 在类范围中, 变量获取其默认值 ,以下给出“未定义引用”错误: 它不是第一个应该以相同的“未定义参考”错误结束吗?还是第二行应该编译?还是我缺少什么? 问题答案: tl; dr 对于 字段 ,是非法的,因为它是对的非法前向引用。您实际上可以通过编写来解决此问题,该文件可以毫无抱怨地进行编译。 对于 局部变量 ,是非法的,因为未在使用前进