当前位置: 首页 > article >正文

J.U.C Review - CAS的工作原理

文章目录

  • 悲观锁与乐观锁
    • 悲观锁
    • 乐观锁
  • CAS(Compare And Swap)
    • CAS的工作流程
    • 示例
  • Java中CAS的实现——Unsafe类
    • `Unsafe`类中的CAS方法
  • 通过`AtomicInteger`类的示例分析CAS操作
  • CAS实现中的问题及解决方案
    • ABA问题
    • 自旋开销
    • 单变量原子操作的局限性

在这里插入图片描述

悲观锁与乐观锁

在并发编程中,锁机制用于控制多个线程对共享资源的访问,防止数据的不一致性。根据锁的处理方式,可以将锁分为两种主要类型:悲观锁和乐观锁。

悲观锁

悲观锁是一种严格的资源访问控制方式,其假设在资源的每次访问时都会发生冲突,因此它总是在访问资源前将其锁定,以确保同一时刻只有一个线程能够访问该资源。这种策略有效防止了并发修改导致的数据不一致性问题,但也可能导致性能瓶颈,尤其在资源访问频繁的情况下。

使用悲观锁时,线程在进入临界区前会尝试获取锁,如果锁已被其他线程占用,则当前线程将进入等待状态,直到获得锁为止。常见的悲观锁实现包括数据库中的行级锁和表级锁、Java中的ReentrantLock等。

适用场景:悲观锁适用于“写多读少”的场景,尤其是在高并发环境下,频繁的写操作容易引发冲突,此时使用悲观锁可以有效避免冲突,保障数据的一致性。


乐观锁

乐观锁与悲观锁相反,它假设资源的访问通常不会发生冲突,因此不需要在每次访问时都加锁。乐观锁允许多个线程同时访问资源,并在操作结束时通过一种验证机制来检查是否发生了冲突,如果检测到冲突,则放弃当前操作并重新尝试。

乐观锁常通过CAS(Compare And Swap,比较并交换)机制来实现,CAS是一种硬件支持的原子操作,保证了即使在多线程环境下,也能安全地执行检查并更新操作。

适用场景:乐观锁适用于“读多写少”的场景,这种情况下,资源访问的冲突概率较低,使用乐观锁可以减少锁带来的开销,提高系统性能。


CAS(Compare And Swap)

CAS,全称为“比较并交换”,是一种用于实现乐观锁的底层机制。在CAS操作中,涉及三个关键的值:

  • V:当前操作的变量。
  • E:预期值(Expected),即操作前希望变量V处于的值。
  • N:新值(New),即希望将变量V更新为的值。

CAS的核心原理是:首先检查变量V是否等于预期值E,如果相等,则将V的值更新为新值N;如果不等,则表示已经有其他线程修改了V的值,当前操作失败,系统将放弃更新。这种机制确保了并发操作的安全性,并且不需要使用传统的锁。


CAS的工作流程

  1. 读取当前变量值V:假设当前线程想要更新一个共享变量V。
  2. 比较V与E:线程将当前的V与预期值E进行比较。如果V等于E,说明没有其他线程修改该变量,操作可以继续。
  3. 执行更新操作:如果V与E相等,线程将V更新为新值N,操作成功。
  4. 处理操作失败:如果V与E不相等,说明有其他线程已经修改了V,此时操作失败,线程可以选择重试或放弃操作。

示例

假设我们有一个共享变量i的初始值为5,线程A希望将其更新为6。线程A将通过以下步骤完成操作:

  1. 读取i的当前值V=5。
  2. 比较i的值是否等于预期值E=5。
  3. 如果i的值确实为5,线程A将其更新为6;如果i的值已经被其他线程修改为其他值(例如2),线程A的操作将失败,i的值保持不变。

因为CAS是一个原子操作,所以在执行步骤2和步骤3时,操作不会被中断,从而保证了操作的线程安全性。

Java中CAS的实现——Unsafe类

Java中通过Unsafe类来实现CAS操作。Unsafe类位于sun.misc包下,提供了一系列的native方法,这些方法由JVM在底层通过C或C++实现,以保证高效的原子操作。

Unsafe类中的CAS方法

Unsafe类中有几个与CAS操作相关的方法:

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

这些方法接收的参数包括:

  • Object o:要操作的对象。
  • long offset:要操作的对象字段的内存偏移量。
  • expected:预期值。
  • x:新值。

这些compareAndSwap方法都是native方法,也就是说,它们的具体实现是在JVM的底层代码中,通常是通过硬件指令(如cmpxchg)来保证操作的原子性。


通过AtomicInteger类的示例分析CAS操作

在Java中,AtomicInteger类是java.util.concurrent.atomic包中的一个类,用于实现整型的原子操作。

在这里插入图片描述

我们通过AtomicIntegergetAndAdd(int delta)方法来深入了解CAS的实现。

public final int getAndAdd(int delta) {
    return U.getAndAddInt(this, VALUE, delta);
}

在上述代码中,U是一个Unsafe对象,VALUEAtomicInteger类中定义的一个常量,表示该对象中目标字段的内存偏移量。

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

getAndAdd方法调用了UnsafegetAndAddInt方法,该方法的源码如下:

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;
}

解析

  1. 获取当前值getIntVolatile(o, offset)读取对象o中偏移量为offset的字段值,并将其赋给v
  2. CAS操作weakCompareAndSetInt(o, offset, v, v + delta)尝试使用CAS将v的值更新为v + delta。如果CAS操作成功,循环结束;否则,继续尝试。
  3. 返回旧值:方法返回的是操作前的旧值v

weakCompareAndSet的特殊性

weakCompareAndSetIntcompareAndSetInt的弱版本,它的执行更轻量级,但在一些情况下可能会失败,而不一定遵循严格的内存顺序。在JDK 9及更高版本中,weakCompareAndSetInt带有@HotSpotIntrinsicCandidate注解,这意味着它可能在JVM中被直接用汇编或IR(中间表示)代码实现,以提高性能。


CAS实现中的问题及解决方案

尽管CAS是实现无锁并发操作的有力工具,但它也存在一些固有的问题。

ABA问题

ABA问题指的是一个变量值从A变为B,又变回A,此时CAS检测不到值的变化,从而错误地认为没有其他线程修改该值。这在某些场景下可能导致逻辑错误。

解决方案:引入版本号或时间戳机制。Java的AtomicStampedReference类提供了解决方案,该类在每次更新值时同时更新版本号,从而保证即使值相同,也能检测到版本号的变化。


自旋开销

由于CAS操作是一个循环重试机制,如果长时间无法成功,可能导致自旋消耗大量CPU资源,降低系统性能。

解决方案:在重试次数超过一定阈值后,线程可以选择放弃CAS操作,转而采用锁机制来保证安全性。


单变量原子操作的局限性

CAS操作只能保证一个变量的原子性,无法直接用于处理多个变量的复合操作。

解决方案:对于多个变量的操作,可以使用锁机制来保证原子性,或者采用AtomicReference类包装对象,通过CAS操作来处理对象的原子性。

在这里插入图片描述


http://www.kler.cn/a/287149.html

相关文章:

  • python蓝桥杯刷题2
  • 基于Java Springboot二手书籍交易系统
  • 区块链中的wasm合约是什么?
  • nodejs入门(1):nodejs的前后端分离
  • 常见网络厂商设备默认用户名/密码大全
  • 十五届蓝桥杯赛题-c/c++ 大学b组
  • CS224W—07 Machine Learning with Heterogeneous Graphs
  • Javaweb12-Maven基础和进阶
  • 【工控】线扫相机小结 第二篇
  • 38道数据分析-Python面试题,程序员面试之前一定要看哦!
  • 深度学习系列72:torch-tensorrt入门
  • uniapp 生成H5 返回上一页 事件不执行
  • Python入门案例01
  • 20240829软考架构-------软考91-95答案解析
  • 视联动力数字科技新成果闪耀2024数博会
  • 科普小课堂:中等硬度的床垫,合适的睡姿,通过日常力量练习提升自身能力以支撑脊柱形态。
  • 【drools】intelj修改JDK版本、进行maven test
  • 业务资源管理模式语言04
  • 【Python-办公自动化】批量修改EXCEL指定内容
  • 牛客周赛 Round 57(A,B,C,D,E,F,G)
  • @Tanstack/vue-query 的使用介绍
  • jQuery基础——开发插件
  • template<typename ... _Args>可变参数模板
  • LiveQing视频点播流媒体RTMP推流服务用户手册-分屏展示:单分屏、四分屏、九分屏、十六分屏、轮巡播放、分组管理、记录加载
  • 001集——CAD—C#二次开发入门——开发环境基本设置
  • 超详细步骤——Keil MDK-ARM 如何修改工程名字