2012-06-04 122 views
11

Enumeration不會拋出ConcurrentModificationException,爲什麼?Java枚舉與迭代器

請參閱下面的代碼。

public static void main(String[] args) { 

    Vector<String> v=new Vector<String>(); 
    v.add("Amit"); 
    v.add("Raj"); 
    v.add("Pathak"); 
    v.add("Sumit"); 
    v.add("Aron"); 
    v.add("Trek"); 

    Enumeration<String> en=v.elements(); 

    while(en.hasMoreElements()) 
    { 
     String value=(String) en.nextElement(); 
     System.out.println(value); 
     v.remove(value); 

    } 

} 

只打印:

 
Amit 
Pathak 
Aron 

這是爲什麼這樣的行爲。我們可以說Enumerator是線程安全的。

編輯:當使用迭代器時,它會在單線程應用程序中引發ConcurrentModificationException

public static void main(String[] args) { 

    Vector<String> v=new Vector<String>(); 
    v.add("Amit"); 
    v.add("Raj"); 
    v.add("Pathak"); 
    v.add("Sumit"); 
    v.add("Aron"); 
    v.add("Trek"); 

    Iterator<String> it=v.iterator(); 
    while(it.hasNext()) 
    { 
     String value=(String) it.next(); 
     System.out.println(value); 
     v.remove(value); 
    } 
} 

請檢查。

+1

請仔細閱讀http://stackoverflow.com/questions/948194/different-using-java-util-enumeration-and-iterator – Garbage

+0

是什麼這個問題的背景是什麼?您是否考慮在多線程環境中使用枚舉而不是迭代器? –

+0

有更多的代碼我們應該知道嗎?在上面的示例代碼中沒有多線程,所以除非您有特別的理由來指責線程安全,否則我認爲您在這裏有點偏離 – posdef

回答

4

枚舉閱讀不會拋出ConcurrentModificationException,爲什麼?

因爲代碼被調用時沒有路徑引發這個異常。 編輯:我指的是由Vector類提供的實現,而不是一般的枚舉接口。

這是爲什麼這樣的行爲。我們可以說Enumerator是線程安全的嗎?

從某種意義上講,它是線程安全的,執行代碼是正確同步。但是,我不認爲你的循環產生的結果是除了你想要的。

您輸出的原因是Enumeration對象維護一個計數器,在每次調用nextElement()後遞增。此計數器不知道您的調用remove()

+0

我的理解是枚舉具有對Iterator的集合的引用。只要我們從載體中移除元素,它就會在枚舉中被注意到。那麼爲什麼它打印3並非全部5如果我們先打印然後移除。 – amicngh

+0

就是這一點。枚舉不會注意到你的調用remove(這會使它減少它的索引變量)。 – MartinK

-1

刪除v.remove(value)如預期

一切都會編輯:對不起,誤讀有

問題這有什麼好做threadsafety雖然。你甚至沒有多線程,所以Java沒有理由爲此拋出異常。

如果你想例外,當你改變了矢量使其不可修改

+4

這似乎並不是對問題的回答 –

+0

Downvote在編輯後刪除.. –

10

請注意,ConcurrentModificationException與多線程或線程安全意義上的併發無關。有些集合允許同時修改,有些則不允許。通常你可以在文檔中找到答案。但併發並不意味着不同線程併發。這意味着您可以在迭代過程中修改集合。

ConcurrentHashMap是一種特殊情況,因爲它在迭代時顯式定義爲線程安全和可編輯的(我認爲所有線程安全集合都是如此)。

無論如何,只要您使用單個線程來迭代和修改集合,ConcurrentHashMap就是您的問題的錯誤解決方案。您錯誤地使用了API。您應該使用Iterator.remove()刪除項目。或者,您可以在迭代和修改原始文件之前製作集合的副本。

編輯:

我不知道,拋出一個ConcurrentModificationException的任何枚舉的。但是,併發修改時的行爲可能不是您所期望的。正如你在例子中看到的那樣,枚舉跳過了列表中的每一個第二個元素。這是由於它的內部索引被增加而不管移除。因此,這是發生了什麼:

  • en.nextElement() - 從向量返回第一個元素,將指數爲1個
  • v.remove(值) - 刪除矢量第一個元素,改變所有元素左
  • en.nextElement() - Vector公司,也就是現在的「帕塔克」

迭代器的快速失敗行爲保護您免受這樣的事情,這就是爲什麼它通常最好是Enumberation返回第二個元素。相反,你應該做到以下幾點:

Iterator<String> it=v.iterator(); 
while(it.hasNext()) 
{ 
    String value=(String) it.next(); 
    System.out.println(value); 
    it.remove(); // not v.remove(value); !! 
} 

或者:

for(String value : new Vector<String>(v)) // make a copy 
{ 
    String value=(String) it.next(); 
    System.out.println(value); 
    v.remove(value); 
} 

第一個當然是更好,因爲你並不真正需要的副本,只要您使用API​​,因爲它是意。

+0

最後,我明白枚舉不會拋出ConcurrentModificationException? – amicngh

+1

+1「ConcurrentModificationException與多線程或線程安全意義上的併發無關」 –

3

這裏的併發修改與線程無關。

此處的併發性僅表示您正在修改集合而您正在迭代它的。 (在你的例子中,這發生在同一個線程中。)

在這種情況下,迭代器和枚舉集合可能會拋出ConcurrentModificationException,但不必這樣做。那些表現快速失敗的行爲。顯然,Vector的列舉不是快速失敗。

線程安全顯然涉及多個線程莫名其妙。 Vector僅在其操作(如add,get等)同步的意義上是線程安全的。這是爲了避免當一個線程正在添加一個元素而另一個線程正在嘗試刪除一個元素時的非確定性行爲。

現在,當一個線程在結構上修改您的集合而另一個線程迭代它時,您必須處理線程安全和併發修改問題。在這種情況下,可能一般情況下,不要依賴ConcurrentModificationException被拋出。最好選擇適當的集合實現(例如,一個線程安全的集合實現),並避免/不允許自己進行併發修改。

一些迭代器允許通過迭代器本身添加/設置/刪除元素。如果你真的需要同時修改,這可能是一個很好的選擇。

1

簡答:這是一個bonus feature,它是在枚舉已經存在之後發明的,因此枚舉器不會拋出它的事實並不表示任何特定的事情。

龍答:
維基百科:

集合實現預JDK 1.2 [...]不包含 集合框架。用於對Java 對象進行分組的標準方法是通過陣列,Vector和Hashtable類 ,這些類不幸地不容易擴展,並且沒有實現 標準成員接口。爲了解決對可重複使用的需求 集合數據結構 集合框架主要由Joshua Bloch設計和開發,並且在JDK 1.2中引入。

當布洛赫隊這樣做,他們認爲這是擺在那發出時,他們的藏品並沒有在多線程程序正確同步報警(ConcurrentModificationException)的新機制是一個好主意。關於這種機制有兩點需要注意:1)不保證捕獲併發錯誤 - 只有在幸運的情況下才會拋出異常。 2)如果您使用單個線程濫用集合(如您的示例中),也會拋出異常。

因此,當多線程訪問時不會拋出ConcurrentModificationException的集合並不意味着它也是線程安全的。

0

這取決於你如何得到枚舉。請看下面的例子,它拋出ConcurrentModificationException

import java.util.*; 

public class ConcurrencyTest { 
    public static void main(String[] args) { 

     Vector<String> v=new Vector<String>(); 
     v.add("Amit"); 
     v.add("Raj"); 
     v.add("Pathak"); 
     v.add("Sumit"); 
     v.add("Aron"); 
     v.add("Trek"); 

     Enumeration<String> en=Collections.enumeration(v);//v.elements(); 

     while(en.hasMoreElements()) 
     { 
      String value=(String) en.nextElement(); 
      System.out.println(value); 
      v.remove(value); 
     }    

     System.out.println("************************************"); 

     Iterator<String> iter = v.iterator(); 
      while(iter.hasNext()){ 
       System.out.println(iter.next()); 
       iter.remove(); 
       System.out.println(v.size()); 
     } 
    } 
} 

枚舉只是一個接口,它的實際行爲是依賴於實現。 Collections.enumeration()調用中的Enumeration在某種程度上包裝了迭代器,因此確實是快速失敗的,但是通過調用Vector.elements()獲得的Enumeration不是。

在未來的未定時間中,非故障快速列舉可以引入任意的非確定性行爲。例如:如果你這樣寫主方法,它會在第一次迭代之後拋出java.util.NoSuchElementException異常。

public static void main(String[] args) { 

     Vector<String> v=new Vector<String>(); 
     v.add("Amit"); 
     v.add("Raj"); 
     v.add("Pathak"); 

     Enumeration<String> en = v.elements(); //Collections.enumeration(v); 

     while(en.hasMoreElements()) 
     { 
      v.remove(0); 
      String value=(String) en.nextElement(); 
      System.out.println(value); 
     }  
    }