Java中的ConcurrentHashMap

Java ConcurrentHashMap類是併發集合類的一部分。它是一個哈希表實現,支持同時檢索和更新。它在多線程環境中用於避免ConcurrentModificationException的出現。

ConcurrentHashMap

如果我們在迭代過程中嘗試修改集合,將會得到ConcurrentModificationException。Java 1.5引入了java.util.concurrent包中的並發類,以克服這種情況。ConcurrentHashMap是允許我們在迭代時修改Map的實現。ConcurrentHashMap的操作是線程安全的。ConcurrentHashMap不允許鍵和值為null。

Java ConcurrentHashMap示例

ConcurrentHashMap類與HashMap類相似,只是它是線程安全的並且允許在迭代時進行修改。

package com.journaldev.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {

	public static void main(String[] args) {

		//ConcurrentHashMap
		Map<String,String> myMap = new ConcurrentHashMap<String,String>();
		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<String,String>();
		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 在遍历时负责处理地图中的新条目,而 HashMap 则抛出 ConcurrentModificationException。让我们仔细查看异常堆栈跟踪。以下语句引发了异常。

String key = it1.next();

这意味着 HashMap 中已插入新条目,但迭代器失败了。实际上,对集合对象的迭代器是fail-fast的,即集合对象结构或条目数量的任何修改都将触发异常。

迭代器如何知道集合中的修改?

我们从 HashMap 中获取键的集合,然后对其进行迭代。HashMap 包含一个变量来计算修改的次数,迭代器在调用其 next() 函数获取下一个条目时使用它。HashMap.java:

/**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See 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");

因為集合已被修改,但其結構仍保持不變,所以不會有任何異常。

進一步閱讀

在創建我們的集合對象和迭代器時,你有注意到那些尖括號嗎?這被稱為泛型,在編譯時進行類型檢查時非常強大,可以在運行時消除ClassCastException。在Java泛型示例中了解更多有關泛型的信息。你還應該閱讀Java集合面試問題Java中的迭代器設計模式

你可以從我們的GitHub存儲庫查看更多Java集合示例。

參考:API文檔

Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java