ConcurrentHashMap 为什么能实现线程安全?
ConcurrentHashMap
是 Java 中线程安全的哈希表实现,通过以下机制保证线程安全:
- 分段锁(Java 1.7):
- 将数据分成多个段(
Segment
),每个段独立加锁,不同段之间可以并发操作。 - 每个段类似于一个小的
Hashtable
,锁的粒度更细,减少竞争。
- CAS +
synchronized
(Java 1.8):
- 放弃分段锁,改用更细粒度的锁(直接锁单个桶的头节点)。
- 使用 CAS(Compare-And-Swap) 无锁操作初始化数组或插入头节点。
- 对链表或红黑树的修改使用
synchronized
锁住头节点,保证单线程操作桶内数据。
- 原子操作与内存可见性:
- 通过
volatile
关键字保证数组元素的可见性。 - 使用原子操作(如
putVal
、replace
)确保复合操作的原子性。
Java 1.7 和 1.8 的 ConcurrentHashMap 核心区别
1. 底层数据结构
版本 | 数据结构 |
---|---|
Java 1.7 | 分段锁(Segment 数组) + HashEntry 数组 + 链表。每个 Segment 独立管理一个哈希表。 |
Java 1.8 | 数组 + 链表 + 红黑树(类似 HashMap 1.8)锁粒度细化到单个桶(头节点)。 |
2. 线程安全机制
版本 | 锁机制 |
---|---|
Java 1.7 | 分段锁(ReentrantLock ):每个 Segment 独立加锁,不同段可并发操作。并发度由 Segment 数量决定(默认 16)。 |
Java 1.8 | CAS + synchronized :- 初始化数组或插入头节点时使用 CAS 无锁操作。 - 修改链表或红黑树时,锁住头节点( synchronized )。- 锁粒度更细,并发度更高。 |
3. 哈希冲突处理
版本 | 冲突处理 |
---|---|
Java 1.7 | 仅支持链表,冲突时新节点插入链表头部(头插法)。 |
Java 1.8 | 链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树;红黑树节点 ≤ 6 时退化为链表。 |
4. 扩容机制
版本 | 扩容方式 |
---|---|
Java 1.7 | 每个 Segment 独立扩容,扩容时锁住整个段。 |
Java 1.8 | 支持多线程协同扩容: - 线程插入数据时发现正在扩容,会帮助迁移数据。 - 使用 ForwardingNode 标记迁移状态。 |
5. 性能优化
版本 | 优化点 |
---|---|
Java 1.7 | 分段锁减少竞争,但并发度受限于段数量(默认 16)。 |
Java 1.8 | - 锁粒度更细(单节点锁),并发度更高。 - 红黑树优化查询效率。 - CAS 减少锁竞争。 |
为什么 Java 1.8 改用 synchronized
而不是 ReentrantLock
?
- 锁粒度细化:
synchronized
在 Java 1.6 后进行了大量优化(如锁升级、偏向锁等),性能接近ReentrantLock
。 - 代码简化:减少内存开销(
ReentrantLock
需要维护AQS
队列)。 - CAS 无锁化:通过 CAS 实现无锁初始化或头节点插入,减少锁的使用场景。
关键代码示例(Java 1.8)
java
java
复制代码final V putVal(K key, V value, boolean onlyIfAbsent) {
// 使用 CAS 插入头节点
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break;
// 锁住头节点修改链表或红黑树
synchronized (f) {
if (tabAt(tab, i) == f) {
// 插入或更新节点
}
}
}
总结对比表
特性 | Java 1.7 | Java 1.8 |
---|---|---|
数据结构 | 分段锁(Segment + HashEntry + 链表) | 数组 + 链表 + 红黑树 |
锁机制 | 分段锁(ReentrantLock) | CAS + synchronized(锁单个桶) |
并发度 | 受限于 Segment 数量(默认 16) | 更高(锁粒度细化到桶) |
哈希冲突处理 | 链表 | 链表 + 红黑树 |
扩容机制 | 段内独立扩容 | 多线程协同扩容 |
内存开销 | 较高(每个 Segment 维护独立哈希表) | 较低 |
为什么 ConcurrentHashMap 线程安全但不需要全局锁?
- 分段锁或细粒度锁:仅锁住需要修改的局部数据(如单个桶),其他线程仍可访问其他部分。
- CAS 无锁操作:在初始化或头节点插入时避免锁竞争。
- 原子性与可见性:通过
volatile
和原子操作保证多线程下的数据一致性。
适用场景
- 高并发读写场景(如缓存、计数器)。
- 替代
Hashtable
或Collections.synchronizedMap
,性能更高。 - 需要保证线程安全且对性能有要求的场景。
注意事项
- 非原子性复合操作:如
putIfAbsent
是原子的,但get
后put
的组合操作仍需外部同步。 - 弱一致性迭代器:迭代器反映的是某一时刻的快照,不保证实时一致性。
通过以上机制,ConcurrentHashMap
在保证线程安全的同时,显著提升了并发性能。