2015-04-18 109 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」與線程無關(直接);它僅僅意味着「同時迭代正在進行中」,而不是「另一個線程修改了列表」。 –

相關問題