Java 8 ConcurrentHashMap 桶级别锁实现机制
Java 8 中的 ConcurrentHashMap 抛弃了分段锁设计,采用了更细粒度的桶级别锁(bucket-level locking)实现,这是其并发性能提升的关键。下面详细解析其实现原理:
1. 基本实现原理
桶级别锁的核心思想是:只锁定当前操作的哈希桶(链表或树的头节点),而不是整个表或某个段。
关键实现方式:
- CAS + synchronized 组合控制
- 锁对象是每个桶的头节点
- 读操作完全无锁(volatile读)
- 写操作仅在必要时加锁
2. 具体实现细节
2.1 锁获取方式
// putVal方法中的锁获取片段
synchronized (f) { // f是桶的头节点if (tabAt(tab, i) == f) { // 双重检查// 执行插入操作...}
}
2.2 锁的获取条件
- 桶不为空时才会加锁
- 只锁住当前桶的头节点
- 通过
synchronized
关键字实现(JVM优化后的偏向锁/轻量级锁)
3. 实现步骤分解
3.1 插入元素时的锁控制流程
- 计算 key 的哈希并定位桶位置
- 如果桶为空,使用 CAS 无锁插入
if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))break;
- 如果桶不为空,则锁住头节点:
synchronized (f) {// 处理链表或树 }
3.2 锁的细粒度验证
synchronized (f) {if (tabAt(tab, i) == f) { // 再次确认当前节点仍是头节点// 实际处理逻辑}
}
这种双重检查确保:
- 防止在获取锁期间桶已被其他线程修改
- 保证操作的原子性
4. 技术优势
-
更高的并发度:
- 理论上并发度 = 哈希桶数量(默认16 → 可能成千上万)
-
更低的锁竞争:
- 不同桶的操作完全并行
- 只有哈希冲突时才会竞争
-
自适应锁优化:
- JVM 会根据竞争情况自动升级锁(偏向锁→轻量级锁→重量级锁)
-
与扩容协同:
- 扩容时通过 ForwardingNode 保证操作可以继续
5. 与 Java 7 分段锁对比
特性 | Java 7 分段锁 | Java 8 桶级别锁 |
---|---|---|
锁粒度 | 段(默认16) | 单个桶 |
锁实现 | ReentrantLock | synchronized |
读操作 | 需要段锁 | 完全无锁 |
扩容 | 段独立扩容 | 整体协同扩容 |
冲突处理 | 链表 | 链表+红黑树 |
6. 实际代码示例
6.1 链表情况下的锁应用
// putVal方法片段
synchronized (f) {if (tabAt(tab, i) == f) {if (fh >= 0) { // 普通链表节点binCount = 1;for (Node<K,V> e = f;; ++binCount) {// 遍历链表...if ((e = e.next) == null) {e.next = new Node<K,V>(hash, key, value, null);break;}}}}
}
6.2 树情况下的锁应用
else if (f instanceof TreeBin) {Node<K,V> p;binCount = 2;if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}
}
7. 关键设计考量
-
synchronized 优化:
- Java 6 后 synchronized 性能已大幅提升
- JVM 会根据竞争情况自动优化
-
CAS 失败回退:
- 当 CAS 插入失败(竞争)时才使用锁
-
死锁避免:
- 只按固定顺序获取单个锁
- 不会出现交叉锁请求
-
内存可见性:
- 通过 volatile 变量保证
- setTabAt/tabAt 使用 Unsafe 保证原子性
这种桶级别锁设计使得 ConcurrentHashMap 在 Java 8 中实现了:
- 更高的并发吞吐量
- 更低的操作延迟
- 更平滑的性能曲线
- 更好的可扩展性