2015-04-18 88 views
25

为什么这段代码不能输入ConcurrentModificationException?它在修改Collection的同时迭代遍历它,而不使用Iterator.remove()方法,它的意思是the only safe way of removing为什么此代码不会引发ConcurrentModificationException?

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

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

编辑

我发现下面的链接

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

相关的部分是

天真的解决方案是商品化检查添加到hasNext在AbstractList中,但这样做会使检查的成本增加一倍。 事实证明,仅在最后一次迭代中进行测试就足够了,这样做几乎不会增加成本。换句话说, 当前实现hasNext的:

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

由该实施方案替换:

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

这种变化将不能进行,因为太阳内部监管机构拒绝了。正式的裁决表明,该变更“具有 ”表明对现有代码具有显着兼容性影响 的潜力。“ (其中“兼容性影响”是修复具有 与 ConcurrentModificationException的更换无声的不当行为的潜力。)

+6

因为'ConcurrentModificationException'被套上一个“尽力而为”的基础上 –

+3

可能重复:预期时java.util.ConcurrentModificationException没有抛出(http://stackoverflow.com/questions/ 24980651/java-util-concurrentmodificationexception -with-thrown-when-expected) – Pshemo

+1

我喜欢Sun没有做出改变的原因是它可能会让一些不好的代码实际上开始抛出它应该抛出的异常 – Mshnik

回答

21

作为一般规则,ConcurrentModificationException s的抛出时所述修饰是检测,不引起。如果你修改后从不访问迭代器,它不会抛出异常。不幸的是,这些细节使得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,进入循环, - > ITER移动到索引0,字符串= “A”,不除去
  2. hasNext()返回true,继续循环 - > ITER移动到索引1 ,string =“B”,删除。 strings现在长度为2.
  3. hasNext()返回false(iter当前处于最后一个索引处,不再有索引),退出循环。

因此,如ConcurrentModificationException s的抛出时next()一个呼叫检测该变形而完成的,这种情况下狭避免了这样的异常。

对于您的其他两个结果,我们确实收到异常。对于"A", "B", "C", "D",删除“B”之后,我们仍然在循环,next()检测ConcurrentModificationException,而对于"A", "B"我想像它是某种多数民众赞成被抓,并再次抛出一个ConcurrentModificationException

+0

但是,如果他们只是在'hasNext()'而不是'next()'中检查并发修改,肯定会抛出?这是一个“尽力而为”的方式吗? –

+0

@pbabcdefp这是“尽力而为”,因为如果该修改未被同步,则不能保证JRE会注意到并发修改。 –

+5

@pbabcdefp:[“尽力而为”](http://en.wikipedia.org/wiki/Best-effort_delivery)并不意味着“我们会尝试并尽我们所能去做所有事情”,就像你可能认为。这意味着他们会尝试,但他们没有任何承诺。 – user2357112

9

hasNext的ArrayIndexOutOfBounds的ArrayList中的迭代器就是

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

remove呼叫后,迭代器在指数2,和列表的大小为2,所以它报告说,迭代完成。没有同时修改检查。使用(“A”,“B”,“C”,“D”或(“A”,“B”),迭代器不在列表的新末尾,因此调用next,并引发异常。

ConcurrentModificationException s为仅协助调试。你不能依靠他们。

1

@Tavian巴恩斯是完全正确的,这种异常不能得到保证,如果有问题的并发修改是不同步的抛出。从报价java.util.ConcurrentModification规格:

注意,快速失败行为不能得到保证,因为它是一般 来说,impossibl e在存在 非同步并发修改时作出任何硬性保证。尽力而为,快速失败操作抛出 ConcurrentModificationException。因此, 在编写依赖于此例外的程序时是错误的: 其正确性:ConcurrentModificationException应仅用于 以检测错误。

Link to JavaDoc for ConcurrentModificationException

+0

请注意,原始帖子中的示例代码是单个线程。这里没有同步问题。 ConcurrentModificationException中的“concurrent”与线程无关(直接);它仅仅意味着“同时迭代正在进行中”,而不是“另一个线程修改了列表”。 –

相关问题