举例说明混合使用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 的锁升级机制
相关推荐
开心猴爷7 分钟前
iOS 代码混淆在项目中的方式, IPA 级保护实践记录
后端
魅影骑士001022 分钟前
柯里化函数
后端·设计模式
JOEH601 小时前
🛡️ 微服务雪崩救星:Sentinel 限流熔断实战,3行代码搞定高可用!
后端·全栈
aiopencode1 小时前
iOS手动代码混淆函数和变量名基本原理和注意事项教程
后端
程序员威哥1 小时前
YOLOv8用ConvMixer结构:简化Backbone,速度+20%,mAP仅降0.9%
后端
开心猴爷1 小时前
如何在苹果手机上面进行抓包?iOS代理抓包,数据流抓包
后端
程序员威哥1 小时前
轻量型YOLO入门:在嵌入式设备上跑通目标检测(树莓派实战)
后端
程序员威哥1 小时前
基于YOLOv7的目标检测实战:彻底解决新手常见的「训练不收敛」问题
后端
程序员威哥1 小时前
从数据集标注到模型评估:YOLO完整工作流实战(附避坑清单)
后端
明月_清风1 小时前
模仿 create-vite / create-vue 风格写一个现代脚手架
前端·后端