Java 并发集合:ConcurrentHashMap 深入解析
1. 概述
ConcurrentHashMap
是 Java 并发包(java.util.concurrent
)中的线程安全 Map
实现,支持高效的并发读写操作,适用于高并发环境。
在 Java 7 和 Java 8 版本中,ConcurrentHashMap
采用了不同的底层实现方式,Java 7 采用分段锁(Segment),而 Java 8 进行了优化,使用 CAS
(Compare-And-Swap)+ synchronized
+ Node
结构。
2. 底层数据结构
Java 7 中的 ConcurrentHashMap(Segment 分段锁机制)
- 采用 Segment + HashEntry 结构。
Segment
继承ReentrantLock
,每个Segment
维护多个HashEntry
。- 通过分段锁机制提高并发性,每个
Segment
维护独立的HashEntry
。 - 读操作无锁,写操作使用
Segment
级别的锁。
Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦初始化就不能改变,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。
Java 8 中的 ConcurrentHashMap(CAS + synchronized 机制)
- 取消了
Segment
,采用 数组 + 链表 + 红黑树 结构。 - 使用
Node<K, V>[] table
作为基础数据结构,链表冲突超过阈值时转换为红黑树。 - 采用
CAS
+synchronized
保证线程安全,提高了性能。
Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 Segment 数组 + HashEntry 数组 + 链表,而是 Node 数组 + 链表 / 红黑树。当冲突链表达到一定长度时,链表会转换成红黑树。
3. 实现原理
1. 读操作(get 方法)
- 读取
table[i]
位置的Node
,如果key
存在,直接返回。 - 在无竞争的情况下,读操作完全无锁。
2. 写操作(put 方法)
- 使用
CAS
操作插入数据,避免不必要的锁开销。 - 若
CAS
失败,使用synchronized
进行加锁。 - 在
Node
链表长度超过 8 时,转换为 红黑树 提高查询效率。
3. 扩容机制(rehash 过程)
- 采用 渐进式扩容,避免
HashMap
扩容时的阻塞问题。 - 扩容时,采用 转移任务拆分 的方式,由多个线程共同完成。
4. 应用场景
ConcurrentHashMap
适用于高并发场景,如:
- 线程安全的 缓存 组件。
- 统计 业务(如用户请求次数统计)。
- 配置存储(如存储系统配置,避免使用
Hashtable
)。
5. 优缺点
优点
✅ 线程安全,支持高并发读写。
✅ 读操作无锁,提高性能。
✅ 写操作部分 CAS
无锁,提高吞吐量。
✅ 采用红黑树优化链表查询效率。
缺点
❌ 不能保证严格的顺序(如 TreeMap
)。
❌ 不能存储 null
key 或 null
value。
❌ 扩容仍然是一个性能瓶颈(尽管已优化)。
6. 替代方案
Collections.synchronizedMap(new HashMap<>())
:简单的同步Map
,性能较低。ConcurrentSkipListMap
:支持 有序 的Map
,适用于排序需求场景。ReadWriteLock + HashMap
:适用于读多写少的场景。
7. 使用示例
import java.util.concurrent.*;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 插入元素
map.put("apple", 10);
map.put("banana", 20);
// 并发读取
System.out.println("Apple count: " + map.get("apple"));
// 并发修改
map.compute("apple", (key, value) -> (value == null) ? 1 : value + 1);
System.out.println("Updated apple count: " + map.get("apple"));
}
}
8. 总结
- Java 8 的
ConcurrentHashMap
采用 CAS + synchronized + 红黑树 提升性能。 - 适用于高并发读写场景,如缓存、计数、共享数据存储。
- 相比
Hashtable
和synchronizedMap
,具有更高的吞吐量和并发能力。 - 需要避免
null
作为键值对,扩容仍需关注性能消耗。
Java7 中 ConcurrentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一但初始化就不能改变。
Java8 中的 ConcurrentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 Segment 数组 + HashEntry 数组 + 链表 进化成了 Node 数组 + 链表 / 红黑树,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。
有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的锁升级。
在多线程环境下,建议优先选择 ConcurrentHashMap
来替代 HashMap
或 Hashtable
,以提高性能和并发安全性。