ConcurrentHashMap
ConcurrentHashMap
是 Java 中用于在多线程环境下高效存储和操作键值对的哈希表实现,下面从基本概念、底层数据结构、线程安全机制、关键方法、性能特点以及使用场景几个方面详细介绍:
基本概念
ConcurrentHashMap
是 java.util.concurrent
包下的一个类,它继承自 AbstractMap
类并实现了 ConcurrentMap
接口。与 HashMap
不同,ConcurrentHashMap
是线程安全的,在多线程环境下可以高效地进行并发操作,避免了 HashMap
在多线程环境下可能出现的数据不一致和线程安全问题。
底层数据结构
JDK 1.7
- 分段锁机制:
ConcurrentHashMap
使用分段锁(Segment)来实现线程安全。整个哈希表被分成多个Segment
,每个Segment
类似于一个小的HashMap
,它继承自ReentrantLock
,可以独立加锁。不同的Segment
可以被不同的线程同时访问,从而提高并发性能。 - 数据结构:每个
Segment
内部包含一个哈希表,由数组和链表组成。
JDK 1.8
- CAS + synchronized:摒弃了分段锁机制,采用
CAS
(Compare-And-Swap,比较并交换)和synchronized
来保证线程安全。 - 数据结构:底层数据结构是数组 + 链表 + 红黑树。当链表长度超过 8 且数组长度大于 64 时,链表会转换为红黑树,以提高查找效率。
线程安全机制
JDK 1.7
- 每个
Segment
都有自己独立的锁,不同的Segment
可以并行操作。当一个线程访问某个Segment
时,会对该Segment
加锁,其他线程可以同时访问其他Segment
,从而提高并发度。
JDK 1.8
- CAS 操作:在插入元素时,首先会使用 CAS 操作尝试更新数组中的节点,如果 CAS 操作成功,则插入成功;如果失败,则说明有其他线程在同时操作,会进入下一步处理。
- synchronized 锁:当发生哈希冲突时,会使用
synchronized
对链表或红黑树的头节点加锁,保证同一时刻只有一个线程可以对该链表或红黑树进行操作。
关键方法
put(K key, V value)
- 作用:将指定的键值对插入到
ConcurrentHashMap
中。 - 实现:在 JDK 1.8 中,首先计算键的哈希值,找到对应的桶位置。如果桶为空,使用 CAS 操作将新节点插入;如果桶不为空,对桶的头节点加锁,遍历链表或红黑树,若键已存在则更新值,否则插入新节点。
get(Object key)
- 作用:根据指定的键获取对应的值。
- 实现:计算键的哈希值,找到对应的桶位置,然后遍历链表或红黑树查找键对应的值。由于
get
操作不需要加锁,所以性能较高。
size()
- 作用:返回
ConcurrentHashMap
中键值对的数量。 - 实现:在 JDK 1.8 中,
size
方法会先尝试使用无锁的方式统计元素数量,如果失败则会加锁统计。
性能特点
- 高并发性能:由于采用了分段锁(JDK 1.7)或 CAS + synchronized(JDK 1.8)的机制,
ConcurrentHashMap
可以支持多个线程同时进行读写操作,并发性能较高。 - 空间开销:相比于
HashMap
,ConcurrentHashMap
为了保证线程安全,会有一定的空间开销,例如分段锁(JDK 1.7)和额外的 CAS 操作。
使用场景
- 多线程环境下的缓存:在多线程环境中,如果需要使用缓存来存储数据,
ConcurrentHashMap
是一个不错的选择,它可以保证线程安全,同时具有较高的并发性能。 - 共享数据存储:当多个线程需要共享一个哈希表时,使用
ConcurrentHashMap
可以避免数据不一致和线程安全问题。
示例代码
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 插入键值对
map.put("apple", 1);
map.put("banana", 2);
map.put("cherry", 3);
// 获取值
Integer value = map.get("banana");
System.out.println("Value of banana: " + value);
// 遍历键值对
for (ConcurrentHashMap.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 删除键值对
map.remove("cherry");
System.out.println("After removing cherry, size: " + map.size());
}
}
通过上述代码可以看到,ConcurrentHashMap
的使用方式与 HashMap
类似,但它在多线程环境下可以保证线程安全。