Java 中的 ConcurrentHashMap
Java ConcurrentHashMap 类是并发集合类的一部分。它是一个哈希表实现,支持并发检索和更新。它用于多线程环境以避免ConcurrentModificationException。
并发哈希映射
如果我们尝试在迭代过程中修改集合,则会得到结果ConcurrentModificationException
。Java 1.5 在包中引入了 Concurrent 类java.util.concurrent
来克服这种情况。ConcurrentHashMap 是 Map 实现,它允许我们在迭代过程中修改 Map。ConcurrentHashMap 操作是线程安全的。ConcurrentHashMap 不允许键和值为 null。
Java ConcurrentHashMap 示例
该类与HashMapConcurrentHashMap
类似,不同之处在于它是线程安全的并且允许在迭代时进行修改。
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,但 Iterator 失败。实际上,Collection 对象上的 Iterator 是快速失败的,即对集合对象中的结构或条目数的任何修改都将触发异常。
迭代器如何知道Collection中的修改?
我们从 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文档