ConcurrentHashMap如何高效加锁?

ConcurrentHashMap 能在高并发环境中保持良好性能,核心原因之一就是其**“细粒度加锁 + 无锁化读操作”**的并发控制策略。它在不同版本中使用了不同的加锁手段:


✅ 一、JDK 1.7:Segment 分段锁(Segment Locking)

🌟 结构:Segment[] + HashEntry[]

  • ConcurrentHashMap 被分为多个 Segment(默认 16 段)

  • 每个 Segment 内部维护一个独立的 Hash 表和 ReentrantLock

  • 多个线程可以并行访问不同 Segment,提高并发度

🧠 加锁策略:

segment.lock();   // 对当前段加独占锁
try {
    // put 操作
} finally {
    segment.unlock();
}

🚫 局限性:

  • Segment 是粗粒度锁:每段仍只能一个线程操作,写热点集中时性能下降

  • 结构复杂,维护成本高


✅ 二、JDK 1.8:CAS + synchronized(锁粒度更细

🌟 结构:Node<K, V>[] table + 链表/红黑树,不再使用 Segment

  • 整体类似 HashMap,但对并发进行了以下优化:


✅ 1. 插入时:使用 CAS + synchronized

步骤(以 putVal() 为例):
  1. 无冲突插入(无锁)

    • 目标桶为空 → 使用 CAS 原子地设置新节点

    if (tabAt(table, i) == null) {
        if (casTabAt(table, i, null, new Node(...))) {
            return;
        }
    }
    
  2. 有冲突时:桶中已有节点 → 进入同步代码块

    synchronized (bin) {
        // 链表插入 or 树节点插入
    }
    

同步锁粒度控制在某个 bucket(bin)级别,而不是整张表或 Segment。


✅ 2. 读操作:大多数是 无锁的

  • 读取时直接使用 volatile 保证可见性,避免加锁开销

  • Nodevalnext 都是 volatile,读线程看到的都是一致数据

  • 特殊情况下(如正在 resize)会短暂阻塞


✅ 3. 扩容时:协作式转移(协同 resize)

  • 多个线程可以共同参与扩容操作,分担迁移任务

  • 避免了单线程 resize 带来的长时间阻塞


✅ 三、小结:如何实现高效加锁?

技术点说明
分段锁(JDK1.7)每个 Segment 独立加锁,允许并发写不同段,减小锁竞争
CAS + synchronized(JDK1.8)空桶用 CAS 插入,无锁,冲突桶只锁当前 bin,锁粒度极细
读操作无锁化大部分读取完全无锁,仅靠 volatile 保证可见性,极大提升读取性能
协作 resize多线程共同扩容,避免长时间阻塞,提高扩容吞吐

✅ 四:实战建议

  • 不要手动对 ConcurrentHashMap 加锁(如自己再包一层 synchronized),会降低并发性能

  • 尽量使用其提供的原子方法,如 putIfAbsent()computeIfAbsent() 来代替复合操作

  • 高并发缓存场景首选 ConcurrentHashMap


如你希望深入解析 JDK8 中具体的 putVal()resize()treeifyBin() 等源码细节,我可以逐行带你阅读并讲解内部机制。是否继续?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值