Java ConcurrentHashMap 深度解析:线程安全与高性能并发
这是文章《Java中的ConcurrentHashMap》的第1部分(共1部分)。
Java的`ConcurrentHashMap`类是并发集合类的重要组成部分。它是一个哈希表的实现,支持高并发的检索和更新操作。在多线程环境中,使用`ConcurrentHashMap`可以有效避免`ConcurrentModificationException`的发生。
ConcurrentHashMap: 并发哈希表详解
当我们在迭代集合的过程中尝试修改集合时,通常会遇到`ConcurrentModificationException`。Java 1.5版本引入了`java.util.concurrent`包中的并发类,旨在解决这类问题。`ConcurrentHashMap`就是其中之一,它允许我们在迭代期间安全地修改`Map`。`ConcurrentHashMap`的所有操作都是线程安全的,并且它不允许键或值为`null`。
Java ConcurrentHashMap 示例与对比
`ConcurrentHashMap`类与`HashMap`类在功能上相似,但其核心区别在于`ConcurrentHashMap`是线程安全的,并且允许在遍历过程中进行修改。下面通过一个Java示例来演示两者的差异。
package com.Olivia.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 迭代前: "+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 迭代后: "+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 迭代前: "+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 迭代后: "+myMap);
}
}
输出结果:
ConcurrentHashMap 迭代前: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap 迭代后: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap 迭代前: {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`内部包含一个用于计算修改次数的变量`modCount`。当调用迭代器的`next()`函数获取下一个条目时,迭代器会使用这个`modCount`来检查集合是否被修改过。以下是`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 迭代前: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap 迭代后: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap 迭代前: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap 迭代后: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}
如果键值被修改会发生什么?
如果我们不添加新条目,而是更新现有的键值对,会发生什么?会抛出异常吗?让我们更改原始程序的代码,然后检查一下。
//myMap.put(key+"new", "new3"); // 注释掉添加新条目的代码
myMap.put(key, "new3"); // 修改现有键的值
由于集合中的元素值发生了变化,但其结构(即元素的数量或哈希桶的分布)保持不变,所以不会有任何异常抛出。
进一步阅读
你在创建集合对象和迭代器时注意到那些尖括号了吗?它被称为泛型(Generics),对于在编译时进行类型检查以减少运行时的`ClassCastException`非常有效。请在Java泛型示例中了解更多关于泛型的内容。你还应该阅读Java集合面试问题和Java中的迭代器设计模式。
你可以从我们的GitHub存储库中查看更多Java集合的示例。
参考资料:API文档