举例说明混合使用CAS和传统锁机制的成功案例

在高并发编程中,混合使用CAS(Compare-And-Swap)和传统锁机制是一种非常成功的策略,它能在保证线程安全的同时,显著提升性能。下面通过几个经典案例来说明这种混合模式的巧妙之处。

🔒 案例一:ConcurrentHashMap(JDK 1.8+)

JDK 1.8 中的 ConcurrentHashMap是混合使用 CAS 和 synchronized锁的典范。

  • CAS 的应用场景​:

    • 初始化和扩容控制 :在初始化哈希表数组或判断是否需要扩容时,会操作一个名为 sizeCtl的变量。代码通过 CAS 操作来原子性地更新 sizeCtl的值,例如尝试将其从一个状态设置为扩容标志。如果 CAS 失败,说明有其他线程已经在进行扩容操作,当前线程便会协助扩容,而不是重复操作或阻塞等待。这避免了使用重量级锁带来的开销。
    • 空桶插入:当向一个空的哈希桶(数组位置)插入新节点时,代码会先使用 CAS 操作尝试将新节点设置到该位置。如果 CAS 成功,则表示插入完成,无需加锁。这在高并发环境下,当哈希冲突不严重时,极大地提升了插入速度。
  • synchronized 锁的应用场景​:

    • 当发生哈希冲突,需要向非空的桶(可能是链表或红黑树)进行插入、更新或删除操作时,ConcurrentHashMap会使用 synchronized关键字锁定这个桶的头节点。锁的粒度非常细,只针对当前操作的桶,其他桶的读写操作不受影响。这种细粒度锁确保了在存在竞争的情况下,对链表或树结构修改的线程安全。

这种设计的好处是:在低竞争 ​(如多数桶为空)情况下,利用无锁的 CAS 获得接近无锁的高性能;在高竞争​(如操作同一个桶)情况下,通过细粒度的同步锁保证数据一致性,实现了性能与安全的平衡。

📊 案例二:LongAdder

LongAdder是针对高并发计数场景设计的类,它通过一种称为"分段"的思想混合了 CAS 和类似锁的竞争机制。

  • 核心思想 :当多个线程同时更新一个计数器时,如果只用一个变量(如 AtomicLong),CAS 竞争会非常激烈,导致大量线程不断重试。LongAdcedr的内部维护了一个基准值(base)和一个动态的单元数组(Cell[])。
  • CAS 的应用 :当线程要增加数值时,首先会尝试使用 CAS 操作更新 base值。如果成功,操作就完成了。
  • ​"分段"锁思想的体现 :如果线程在更新 base时 CAS 失败,说明发生了竞争。它不会一直自旋,而是会根据自己的哈希值映射到 Cell数组中的某个单元,然后尝试对这个单元内的变量进行 CAS 更新。这样,就将对单一热点的竞争分散到了多个单元上。
  • 退避策略 :如果对某个 Cell的 CAS 操作也失败了,LongAdder并不会让线程无限重试,而是会尝试扩容 Cell数组,进一步分散竞争。这类似于一种轻量的"锁升级"策略,通过扩大资源来减少冲突。

最终,获取总值时,只需将 base和所有 Cell的值累加即可。这种"分散竞争"的策略,使得 LongAdder在高并发写场景下的性能远高于 AtomicLong,是典型的以空间换时间的成功案例。

🔄 案例三:锁升级机制(JVM层面)

Java 虚拟机(JVM)内部的 synchronized 锁优化策略------锁升级,本身就是一种根据竞争强度动态混合无锁、CAS 和重量级锁的机制。

  1. 偏向锁(可视为一种乐观无锁优化)​:初期,锁会偏向于第一个获得它的线程。之后该线程再进入同步块时,无需任何同步操作(如 CAS),仅仅检查对象头的标记即可,开销极小。这适用于几乎没有锁竞争的场景。
  2. 轻量级锁(主要依赖 CAS)​ :当有第二个线程尝试获取锁时,偏向锁升级为轻量级锁。线程会通过 CAS 操作尝试在栈帧中创建锁记录(Lock Record)并更新对象头。如果成功,则获得锁;如果失败,则会自旋重试一定次数。
  3. 重量级锁(传统互斥锁)​:如果自旋重试超过一定次数(或等待线程过多),锁会升级为重量级锁。未能获取锁的线程会被挂起,进入阻塞队列,等待操作系统的调度唤醒。这时,synchronized 就表现为一个真正的互斥锁。

这个过程完美体现了混合策略的精髓:​根据实时竞争情况,从开销最小的方案(偏向锁)平滑过渡到最安全的方案(重量级锁)​,在绝大多数时间避免了昂贵的线程阻塞和唤醒。

💎 总结与模式提炼

从这些成功案例中,我们可以提炼出混合使用 CAS 和锁的通用模式:

策略 核心思想 适用场景 案例体现
CAS 探路,锁保安全 先尝试低开销的 CAS,失败后再使用锁。 常见路径无竞争,但可能存在冲突。 ConcurrentHashMap的空桶插入
分散竞争,分而治之 将单一竞争点拆分为多个,降低冲突概率。 对单一热点资源的高频写操作。 LongAdder的分段计数
动态升级,按需适配 根据竞争强度,从轻量级方案平滑过渡到重量级方案。 竞争强度难以预测或会动态变化。 JVM 的锁升级机制
相关推荐
张较瘦_4 小时前
Springboot | 初识Springboot 从“手动做饭”到“点外卖”的编程革命
java·spring boot·后端
间彧4 小时前
在高并发场景下,如何评估是使用CAS还是传统锁机制更合适?
后端
间彧4 小时前
在高并发场景下,如何量化评估何时应该从CAS切换到传统锁机制?
后端
oak隔壁找我4 小时前
SpringBoot 整合 Minio 和 FastDFS 实现分布式文件存储
java·后端
间彧5 小时前
CAS技术原理与应用详解
后端
华仔啊5 小时前
35岁程序员失业了,除了送外卖,还能做什么?
前端·后端·程序员
SimonKing5 小时前
【开发者必备】Spring Boot 2.7.x:WebMvcConfigurer配置手册来了(二)!
java·后端·程序员
程序员飞哥5 小时前
别再说“对接接口没技术含量了”,这才是高手的打开方式!
后端·程序员
DokiDoki之父5 小时前
Spring—容器
java·后端·spring