Java中的ConcurrentHashMap

Java ConcurrentHashMap类是并发集合类的一部分。它是哈希表的实现,支持并发检索和更新。在多线程环境中,它用于避免ConcurrentModificationException

ConcurrentHashMap

如果我们在迭代过程中尝试修改集合,将会抛出ConcurrentModificationException异常。Java 1.5引入了java.util.concurrent包中的并发类,以解决这种情况。ConcurrentHashMap是允许我们在迭代过程中修改Map的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,但迭代器失败了。实际上,对集合对象的迭代器是快速失败的,即对集合对象的结构或条目数进行任何修改都会触发异常。

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

我们已经从 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