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文档

bannerAds