hashmap遍歷時用map.remove方法為什麼會報錯?

yflconding發表於2019-04-03

發現問題

筆者最近在除錯專案bug的時候,遇到了一個很奇怪的bug,就是在對hashmap集合進行遍歷的時候,同時做了remove操作,這個操作最後導致丟擲了java.util.ConcurrentModificationException的錯誤。
帶著疑惑,下面參考著原始碼,分析問題的原因。
首先,重現問題,構造一個map並往裡面加元素:

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錯誤

    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遍歷時用map.remove方法為什麼會報錯?

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

hashmap遍歷時用map.remove方法為什麼會報錯?

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

hashmap遍歷時用map.remove方法為什麼會報錯?

問題原因

可以看出迭代器初始化的時候就對modCount和expectedModCount進行同步。
到此,可以看出報錯的原因:

  • hashmap裡維護了一個modCount變數,迭代器裡維護了一個expectedModCount變數,一開始兩者是一樣的。
  • 每次進行hashmap.remove操作的時候就會對modCount+1,此時迭代器裡的expectedModCount還是之前的值。
  • 在下一次對迭代器進行next()呼叫時,判斷是否HashMap.this.modCount != this.expectedModCount,如果是則丟擲異常。

解決問題

那什麼情況下在遍歷的時候可以刪除map裡面的元素呢?看下迭代器提供的remove方法:

hashmap遍歷時用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即可以知道有其他執行緒修改了集合。

替換機制:

  • 用迭代器的remove方法。
  • 用currentHashMap替代HashMap

相關文章