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