標籤:
如果你是一名Java開發人員,我能夠確定你肯定知道ConcurrentModificationException,它是在使用迭代器遍曆集合對象時修改集合對象造成的(並發修改)異常。實際上,Java的集合架構是迭代器設計模式的一個很好的實現。
Java 1.5引入了java.util.concurrent包,其中Collection類的實現允許在運行過程中修改集合對象。ConcurrentHashMap是一個與HashMap很相似的類,但是它支援在運行時修改集合對象。
讓我們通過一個簡單的程式來協助理解:
package com.king.hashmap;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * ConcurrentHashMap可以支援向map中添加新元素 * HashMap則拋出了ConcurrentModificationException */public class ConcurrentHashMapExample { public static void main(String[] args) { //ConcurrentHashMap Map<String, String> myMap = new ConcurrentHashMap<>(); myMap.put("1", "1"); myMap.put("2", "1"); myMap.put("3", "1"); myMap.put("4", "1"); myMap.put("5", "1"); myMap.put("6", "1"); System.out.println("ConcurrentHashMap before iterator: " + myMap); Iterator<String> it = myMap.keySet().iterator(); while (it.hasNext()) { String key = it.next(); if (key.equals("3")) myMap.put(key + "new", "new3"); } System.out.println("ConcurrentHashMap after iterator: " + myMap); //HashMap myMap = new HashMap<>(); myMap.put("1", "1"); myMap.put("2", "1"); myMap.put("3", "1"); myMap.put("4", "1"); myMap.put("5", "1"); myMap.put("6", "1"); System.out.println("HashMap before iterator: " + myMap); Iterator<String> it1 = myMap.keySet().iterator(); while (it1.hasNext()) { String key = it1.next(); if (key.equals("3")) myMap.put(key + "new", "new3"); } System.out.println("HashMap after iterator: " + myMap); }}
當我們試著運行上面的程式,輸出如下:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}Exception in thread "main" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793) at java.util.HashMap$KeyIterator.next(HashMap.java:828) at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)
查看輸出,很明顯ConcurrentHashMap可以支援向map中添加新元素,而HashMap則拋出了ConcurrentModificationException。
查看異常堆棧記錄,可以發現是下面這條語句拋出異常:
String key = it1.next();
這就意味著新的元素在HashMap中已經插入了,但是在迭代器執行時出現錯誤。事實上,集合對象的迭代器提供快速失敗(Fail-Fast)的機制,即修改集合對象結構或者元素數量都會使迭代器觸發這個異常。
但是迭代器是怎麼知道HashMap被修改了呢,我們可以一次取出HashMap的所有Key然後進行遍曆。
HashMap包含一個修改計數器,當你調用它的next()方法來擷取下一個元素時,迭代器將會用到這個計數器。
/** * HashMap結構的修改次數 * 結構修改是指:改變了HashMap中mapping的個數或者其中的內部結構(比如,重新計算hash值) * 這個欄位在通過Collection操作Hashmap時提供快速失敗(Fail-fast)功能。 * (參見 ConcurrentModificationException)。 */transient volatile int modCount;
現在為了證明上面的觀點,我們對原來的代碼做一點修改,使迭代器在插入新的元素後跳出迴圈。只要在調用put方法後增加一個break:
if(key.equals("3")){ myMap.put(key+"new", "new3"); break;}
再執行修改後的代碼,會得到下面的輸出結果:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}
最後,如果我們不添加新的元素而是修改已經存在的索引值對會不會拋出異常呢?修改原來的程式並且自己驗證一下:
//myMap.put(key+"new", "new3");myMap.put(key, "new3");
再執行修改後的代碼,會得到下面的輸出結果:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}ConcurrentHashMap after iterator: {3new=new3, 1=1, 5=1, 6=1, 3=1, 4=1, 2=1}HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}HashMap after iterator: {3=new3, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap vs ConcurrentHashMap — 樣本及Iterator探秘