hashmap遍歷時用map.remove方法爲何會報錯?

發現問題

筆者最近在調試項目bug的時候,遇到了一個很奇怪的bug,就是在對hashmap集合進行遍歷的時候,同時作了remove操做,這個操做最後致使拋出了java.util.ConcurrentModificationException的錯誤。
帶着疑惑,下面參考着源碼,分析問題的緣由。
首先,重現問題,構造一個map並往裏面加元素:java

private static HashMap<Integer, String> map = new HashMap<Integer, String>();;
	public static void main(String[] args) {
  	        for(int i = 0; i < 10; i++){  
	            map.put(i, "value" + i);  
	        }  
	}
複製代碼

而後移除一些元素,此時就會報java.util.ConcurrentModificationException錯誤bash

for(Map.Entry<Integer, String> entry : map.entrySet()){  
         Integer key = entry.getKey();  
         if(key % 2 == 0){  
             System.out.println("To delete key " + key);  
             map.remove(key);  
             System.out.println("The key " + + key + " was deleted");  
         }  
複製代碼

報錯

分析問題

從報錯中能夠看出,HashMap$HashIterator.nextNode這個方法有代碼錯誤了,點進去看,大概知道HashMap.this.modCount != this.expectedModCount 成立併發

再看一下hashmap的remove操做是作了什麼:高併發

這裏對modCount進行了自增操做,表示操做動做+1。再看modCount和expectedModCount是什麼東西this

問題緣由

能夠看出迭代器初始化的時候就對modCount和expectedModCount進行同步。
到此,能夠看出報錯的緣由:spa

  • hashmap裏維護了一個modCount變量,迭代器裏維護了一個expectedModCount變量,一開始二者是同樣的。
  • 每次進行hashmap.remove操做的時候就會對modCount+1,此時迭代器裏的expectedModCount仍是以前的值。
  • 在下一次對迭代器進行next()調用時,判斷是否HashMap.this.modCount != this.expectedModCount,若是是則拋出異常。

解決問題

那什麼狀況下在遍歷的時候能夠刪除map裏面的元素呢?看下迭代器提供的remove方法:線程

能夠看出迭代器裏remove了一個元素以後會對expectedModCount從新賦值,這樣再次遍歷的時候就不會報錯了。因此以前的代碼能夠改爲以下寫法,直接調用迭代器的remove方法。調試

Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
      while(it.hasNext()){
          Map.Entry<Integer, String> entry = it.next();
          Integer key = entry.getKey();
          if(key % 2 == 0){
         	 System.out.println("To delete key " + key);
         	 it.remove();    
         	 System.out.println("The key " + + key + " was deleted");

          }
      }
複製代碼

總結

  • 基本上java集合類(包括list和map)在遍歷時沒用迭代器進行刪除了都會報ConcurrentModificationException錯誤,這是一種fast-fail的機制,初衷是爲了檢測bug。
  • 通俗一點來講,這種機制就是爲了防止高併發的狀況下,多個線程同時修改map或者list的元素致使的數據不一致,這是隻要判斷當前modCount != expectedModCount便可以知道有其餘線程修改了集合。

替換機制:code

  • 用迭代器的remove方法。
  • 用currentHashMap替代HashMap
相關文章
相關標籤/搜索