Java ConcurrentHashMap 源码解析:JDK 1.8 中 CAS+synchronized 实现并发安全的逻辑
·
Java ConcurrentHashMap 源码解析:JDK 1.8 的 CAS+synchronized 实现
1. 核心设计思想
JDK 1.8 的 ConcurrentHashMap 采用 分段锁粒度优化 策略:
- 摒弃旧版的分段锁(Segment)
- 对每个桶(数组元素)独立加锁
- 通过
CAS实现无锁化初始化/扩容 - 通过
synchronized锁定桶首节点保证写安全
2. 关键数据结构
transient volatile Node<K,V>[] table; // 哈希桶数组
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
- 桶数组用
volatile保证内存可见性 - 节点设计为链表结构(超过阈值转红黑树)
3. 并发安全实现逻辑
(1) put() 操作流程
final V putVal(K key, V value, boolean onlyIfAbsent) {
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 桶数组初始化(CAS保证原子性)
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// CASE 1: 目标桶为空(CAS写入首节点)
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
break; // CAS成功则退出
}
// CASE 2: 桶处于迁移状态(协助扩容)
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// CASE 3: 桶非空(synchronized锁定首节点)
else {
synchronized (f) { // 锁住桶首节点
if (tabAt(tab, i) == f) { // 双重检查
// 链表插入/更新
if (fh >= 0) { ... }
// 红黑树处理
else if (f instanceof TreeBin) { ... }
}
}
}
}
}
(2) CAS 关键操作
tabAt():原子获取桶元素(通过Unsafe.getObjectVolatile)casTabAt():CAS 替换桶元素(核心方法)
static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
Node<K,V> c, Node<K,V> v) {
return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
}
(3) 锁粒度控制
synchronized锁定桶首节点 而非整个表- 并发场景:不同线程可同时操作不同桶
- 锁冲突概率:$P_{conflict} = \frac{N_{active-threads}}{N_{buckets}}$
4. 扩容机制(多线程协作)
graph LR
A[线程触发扩容] --> B[创建新数组 nextTable]
B --> C[设置 ForwardingNode 标记]
C --> D[其他线程 put 时检测到标记]
D --> E[协助数据迁移]
E --> F[迁移完成替换 table]
- ForwardingNode:特殊节点类型,表示桶正在迁移
- 迁移协作:线程通过
transfer()分担数据迁移任务
5. 性能优化点
-
读操作完全无锁:
- 依赖
volatile变量和节点设计的可见性 - 红黑树查找通过
TreeBin的读写锁分离
- 依赖
-
写冲突解决:
- 桶空时:CAS 竞争(轻量级)
- 桶非空:synchronized 锁(悲观锁)
-
扩容并行化: $$ T_{resize} \approx \frac{Size}{N_{threads} \cdot C_{batch}} $$ 其中 $C_{batch}$ 是单次迁移的桶数量
6. 与旧版对比
| 特性 | JDK 1.7 | JDK 1.8 |
|---|---|---|
| 锁粒度 | Segment(多个桶) | 单桶首节点 |
| 数据结构 | 数组+链表 | 数组+链表/红黑树 |
| 扩容 | 单线程 | 多线程协作 |
| 读性能 | 需遍历链表的 volatile 读 | 完全无锁 |
总结:JDK 1.8 通过
CAS+synchronized的分级锁策略,在保证线程安全的同时,将并发冲突概率降至 $O(1/N_{buckets})$ 级别,实现了高并发场景下的吞吐量优化。
更多推荐
所有评论(0)