为什么这段代码不会抛出ConcurrentModificationException?

时间:2015-04-18 22:10:23

标签: java

为什么这段代码不会抛出ConcurrentModificationException?它会在迭代通过它时修改Collection,而不使用Iterator.remove()方法,这意味着the only safe way of removing

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的。)

3 个答案:

答案 0 :(得分:22)

作为一般规则,当检测到,而不是导致时,会抛出ConcurrentModificationException个。如果您在修改后从未访问过迭代器,则不会抛出异常。遗憾的是,这个细节使得ConcurrentModificationException对于检测数据结构的滥用是相当不可靠的,因为它们只是在损坏完成后被抛出。

此方案不会抛出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,输入循环, - &gt; iter移动到索引0,字符串=&#34; A&#34;,未删除
  2. hasNext()返回true,继续循环 - &gt; iter移动到索引1,string =&#34; B&#34;,删除。 strings现在的长度为2。
  3. hasNext()返回false(iter当前位于最后一个索引,不再有索引),退出循环。
  4. 因此,当ConcurrentModificationException的调用检测到已经进行了修改时,抛出next(),这种情况可以避免这种异常。

    对于您的其他两个结果,我们会得到例外。对于"A", "B", "C", "D",删除&#34; B&#34;我们仍在循环中,next()检测到ConcurrentModificationException,而对于"A", "B",我想象它是某种被捕获的ArrayIndexOutOfBounds并以ConcurrentModificationException

    重新抛出

答案 1 :(得分:9)

ArrayList的迭代器中的

hasNext只是

public boolean hasNext() {
    return cursor != size;
}

remove调用之后,迭代器在索引2处,并且列表的大小为2,因此它报告迭代已完成。没有并发修改检查。使用(“A”,“B”,“C”,“D”或(“A”,“B”),迭代器不在列表的新末尾,因此调用next,并且抛出异常。

ConcurrentModificationException只是一个调试辅助工具。你不能依赖它们。

答案 2 :(得分:1)

@Tavian Barnes是完全正确的。如果有问题的并发修改未同步,则无法保证抛出此异常。引自java.util.ConcurrentModification规范:

  

请注意,通常情况下无法保证快速失败的行为   说话,在场的情况下不可能做出任何硬性保证   不同步的并发修改。失败快速操作投掷   ConcurrentModificationException是尽力而为的。因此,它   编写一个依赖于此异常的程序是错误的   它的正确性:应该只使用ConcurrentModificationException   检测错误。

Link to JavaDoc for ConcurrentModificationException