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. 性能优化点
  1. 读操作完全无锁

    • 依赖 volatile 变量和节点设计的可见性
    • 红黑树查找通过 TreeBin 的读写锁分离
  2. 写冲突解决

    • 桶空时:CAS 竞争(轻量级)
    • 桶非空:synchronized 锁(悲观锁)
  3. 扩容并行化: $$ 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})$ 级别,实现了高并发场景下的吞吐量优化。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐