【并发编程】CAS到底是什么

这篇具有很好参考价值的文章主要介绍了【并发编程】CAS到底是什么。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

优秀借鉴

Java实现CAS的原理 | Java程序员进阶之路

美团终面:CAS确定完全不需要锁吗?

1、什么是CAS

CASCompare-And-Swap(比较并交换)的缩写,是一种轻量级的同步机制,主要用于实现多线程环境下的无锁算法和数据结构,保证了并发安全性。它可以在不使用锁(如synchronized、Lock)的情况下,对共享数据进行线程安全的操作。

CAS操作主要有三个参数:要更新的内存位置、期望的值和新值。CAS操作的执行过程如下:

  1. 首先,获取要更新的内存位置的值,记为var。
  2. 然后,将期望值expectedvar进行比较,如果两者相等,则将内存位置的值var更新为新值new。
  3. 如果两者不相等,则说明有其他线程修改了内存位置的值var,此时CAS操作失败,需要重新尝试。

2、原理相关的Unsafe类

前面提到,CAS是一种原子操作。那么Java是怎样来使用CAS的呢?我们知道,在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现。

Unsafe类是JDK提供的一个不安全的类,它提供了一些底层的操作,包括内存操作、线程调度、对象实例化等。它的作用是让Java可以在底层直接操作内存,从而提高程序的效率。但是,由于Unsafe类是不安全的,所以只有JDK开发人员才能使用它,普通开发者不建议使用。它里面大多是一些native方法,其中就有几个关于CAS的:

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

具体的实现过程如下:

  1. 调用compareAndSwapIntcompareAndSwapLongcompareAndSwapObject方法时,会传入三个参数,分别是需要修改的变量V、期望的值A和新值B。
  2. 方法会先读取变量V的当前值,如果当前值等于期望的值A,则使用新值B来更新变量V,否则不做任何操作。
  3. 方法会返回更新操作是否成功的标志,如果更新成功,则返回true,否则返回false。

由于CAS操作是基于底层硬件支持的原子性指令来实现的,所以它可以保证操作的原子性和线程安全性,同时也可以避免使用锁带来的性能开销。因此,CAS操作广泛应用于并发编程中,比如实现无锁数据结构、实现线程安全的计数器等。

3、原子操作类解析

前面说到Unsafe类中的几个支持CAS的方法,而在Java中便有这么一些类应用到了这些方法,其中一类便是原子操作相关类,在juc包中的atomic包下。

这里我们以AtomicInteger类的getAndAdd(int delta)方法为例,来看看Java是如何实现原子操作的:

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

这里的U其实就是一个Unsafe对象:

private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();

所以其实AtomicInteger类的getAndAdd(int delta)方法是调用Unsafe类的方法来实现的:

@HotSpotIntrinsicCandidate
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;
}

前面我们讲到,CAS是“无锁”的基础,它允许更新失败。所以经常会与while循环搭配,在失败后不断去重试。这里声明了一个v,也就是要返回的值。从getAndAddInt来看,它返回的应该是原来的值,而新的值的v + delta

这里使用的是do-while循环。这种循环不多见,它的目的是保证循环体内的语句至少会被执行一遍。这样才能保证return 的值v是我们期望的值。

public final boolean weakCompareAndSetInt(Object o, long offset, int expected, int x) {
    return compareAndSetInt(o, offset, expected, x);
}

public final native boolean compareAndSetInt(Object o, long offset, int expected, int x);

可以看到,最终其实是调用的我们之前说到了CAS native方法。那为什么要经过一层weakCompareAndSetInt呢?

根据深入浅出 Java 多线程中介绍可知,这是与volatile关键字有关的。weakCompareAndSet操作仅保留了volatile自身变量的特性,而除去了happens-before规则带来的内存语义。也就是说,weakCompareAndSet无法保证处理操作目标的volatile变量外的其他变量的执行顺序( 编译器和处理器为了优化程序性能而对指令序列进行重新排序 ),同时也无法保证这些变量的可见性。这在一定程度上可以提高性能。

4、ABA问题

4.1、何为ABA

ABA问题指在CAS操作过程中,如果变量的值被改为了 A、B、再改回 A,而CAS操作是能够成功的,这时候就可能导致程序出现意外的结果。

在高并发场景下,使用CAS操作可能存在ABA问题,也就是在一个值被修改之前,先被其他线程修改为另外的值,然后再被修改回原值,此时CAS操作会认为这个值没有被修改过,导致数据不一致。

4.2、解决方案

为了解决ABA问题,Java中提供了AtomicStampedReference类,该类通过使用版本号的方式来解决ABA问题。每个共享变量都会关联一个版本号,CAS操作时需要同时检查值和版本号是否匹配。因此,如果共享变量的值被改变了,版本号也会发生变化,即使共享变量被改回原来的值,版本号也不同,因此CAS操作会失败。

下面是一个使用AtomicStampedReference类解决ABA问题的示例代码:

public void test() {
    AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(1, 0);
    // 重现ABA问题
    int oldStamp = atomicStampedRef.getStamp();
    int oldValue = atomicStampedRef.getReference();
    // 将值从 1 改为 2,并使版本号自增
    atomicStampedRef.compareAndSet(oldValue, 2, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
    // 将值从 2 改回 1,并使版本号自增
    atomicStampedRef.compareAndSet(2, 1, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
    
    // 使用旧版本号修改时,即使值和旧值一样,但版本号已经发生了变化,导致修改失败
    atomicStampedRef.compareAndSet(2, 1, oldStamp, oldStamp + 1);
}

在上面的示例中,通过getStamp()getReference()方法分别获取共享变量的版本号和值,然后使用compareAndSet()方法进行CAS操作,每次操作都会更新版本号。这样,就可以避免ABA问题。

5、CPU空转

5.1、为什么出现CPU空转

除了ABA问题,CAS操作还可能会受到自旋时间过长的影响,因为如果某个线程一直在自旋等待,会浪费CPU资源。

5.2、解决方案

为了解决上述问题,可以采用自适应自旋锁的方式,即在前几次重试时采用忙等待的方式,后面则使用阻塞等待的方式,避免浪费CPU资源。

public class SpinLock {
    private AtomicReference<Thread> owner = new AtomicReference<>();
    private int count;

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 已经获取了锁
        if (owner.get() == currentThread) { 
            count++;
            return;
        }
        // 自旋等待获取锁
        while (!owner.compareAndSet(null, currentThread)) {
            // 自适应自旋
            if (count < 10) {
                count++;
            } else {
                // 阻塞等待
                LockSupport.park(currentThread);
            }
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 当前线程持有锁
        if (owner.get() == currentThread) { 
            if (count > 0) {
                count--;
            } else {
                // 释放锁
                owner.compareAndSet(currentThread, null);
                // 唤醒其他线程
                LockSupport.unpark(currentThread);
            }
        }
    }
}

这里解释一下上面代码:

在设计时使用了AtomicReference来保存当前持有锁的线程对象,这样可以保证线程安全。

当一个线程请求获取锁时,如果当前线程已经持有锁,则将计数器加1,否则使用CAS操作来获取锁。这样可以避免了使用synchronized关键字或者ReentrantLock等锁的实现机制。

当线程获取锁失败时,使用自旋等待的方式,这样可以避免线程进入阻塞状态,避免了线程上下文切换的开销。当重试次数小于10时,使用自旋等待的方式,当重试次数大于10时,则使用阻塞等待的方式。这样可以在多线程环境下保证线程的公平性和效率。

释放锁时,如果计数器大于0,则将计数器减1,否则将锁的拥有者设为null,唤醒其他线程。这样可以确保在有多个线程持有锁的情况下,正确释放锁资源,并唤醒其他等待线程,保证线程的正确性和公平性。

6、应用场景

CAS在多线程并发编程中被广泛应用,它通常用于实现乐观锁和无锁算法。以下是CAS的一些应用场景:

  1. 线程安全计数器:由于CAS操作是原子性的,因此CAS可以用来实现一个线程安全的计数器;
  2. 队列:在并发编程中,队列经常用于多线程之间的数据交换。使用CAS可以实现无锁的非阻塞队列(Lock-Free Queue);
  3. 数据库并发控制:乐观锁就是通过CAS实现的,它可以在数据库并发控制中保证多个事务同时访问同一数据时的一致性;
  4. 自旋锁:自旋锁是一种非阻塞锁,当线程尝试获取锁时,如果锁已经被其他线程占用,则线程不会进入休眠,而是一直在自旋等待锁的释放。自旋锁的实现可以使用CAS操作;
  5. 线程池:在多线程编程中,线程池可以提高线程的使用效率。使用CAS操作可以避免对线程池的加锁,从而提高线程池的并发性能。

7、CAS真的完全没加锁吗?

上面说过,CAS是一个无锁机制的操作,底层是通过Unsafe类使用native本地方法进行的CAS操作,但是大家有没有想过这样的问题:硬件层面CAS又是如何保证原子性的呢?真的完全没加锁吗?

拿比较常见的x86架构的CPU来说,其实 CAS 操作通常使用 cmpxchg 指令实现的。

可是为啥 cmpxchg 指令能保证原子性呢?主要是有以下几个方面的保障:

  1. cmpxchg 指令是一条原子指令。在 CPU 执行 cmpxchg 指令时,处理器会自动锁定总线,防止其他 CPU 访问共享变量,然后执行比较和交换操作,最后释放总线。
  2. cmpxchg 指令在执行期间,CPU 会自动禁止中断。这样可以确保 CAS 操作的原子性,避免中断或其他干扰对操作的影响。
  3. cmpxchg 指令是硬件实现的,可以保证其原子性和正确性。CPU 中的硬件电路确保了 cmpxchg 指令的正确执行,以及对共享变量的访问是原子的。

所以,在操作系统层面,CAS还是会加锁的,通过加锁的方式锁定总线,避免其他CPU访问共享变量。文章来源地址https://www.toymoban.com/news/detail-831218.html

到了这里,关于【并发编程】CAS到底是什么的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JUC并发编程学习笔记(十八)深入理解CAS

    什么是CAS 为什么要学CAS:大厂你必须深入研究底层!有所突破! java层面的cas-------compareAndSet compareAndSet(int expectedValue, int newValue) 期望并更新,达到期望值就更新、否则就不更新! Unsafe类 java不能直接操作内存,但是可以调用c++,c++可以操作内存,java可以通过native定义

    2024年02月05日
    浏览(60)
  • 「JUC并发编程」初识CAS锁(概述、底层原理、原子引用、自旋锁、缺点)

    概述 CAS的全称为 Compare-And-Swap ,直译就是 对比交换 。是一条 CPU的原子指令 ,其作用是让CPU 先 进行比较两个值是否相等, 然后 原子地更新某个位置的值。经过调查发现,其实现方式是 基于硬件平台的汇编指令 ,就是说CAS是靠硬件实现的, JVM只是封装了汇编调 用,那些

    2024年02月16日
    浏览(51)
  • 《JUC并发编程 - 高级篇》05 -共享模型之无锁 (CAS | 原子整数 | 原子引用 | 原子数组 | 字段更新器 | 原子累加器 | Unsafe类 )

    有如下需求,保证 account.withdraw 取款方法的线程安全 原有实现并不是线程安全的 测试代码 执行测试代码,某次执行结果 5.1.1 为么不安全 withdraw 方法是临界区,会存在线程安全问题 查看下字节码 多线程在执行过程中可能会出现指令的交错,从而结果错误! 5.1.2 解决思路1

    2023年04月12日
    浏览(46)
  • 通俗解释 JVM CAS 机制

    JVM 的 CAS,即 Compare And Swap,是一种并发编程中常用的保证原子性的技术。 通俗地讲,CAS 用于在多线程环境下对共享变量的值进行原子操作,即多个线程同时访问同一个共享变量,但只有一个线程能修改这个变量的值,并且这个操作是原子的,不会被其他线程打断。 具体来说

    2023年04月10日
    浏览(34)
  • 【并发知识点】CAS的实现原理及应用

    AQS的实现原理及应用 CAS的实现原理及应用 本章节介绍CAS概念、实现原理,并通过java代码应用,最终模拟赛龙舟比赛。 CAS的全称为:CompareAndSwap,直译为对比和交换。 CAS实际是普遍处理器都支持的一条指令,这条指令通过判断当前内存值V、旧的预期值A、即将更新的值B是否相

    2024年02月10日
    浏览(50)
  • Java的CAS操作

    技术是为了解决问题而生的,通过 CAS 我们可以以无锁的方式,保证对共享数据进行 “读取 - 修改 - 写回” 操作序列的正确性。 CAS 是乐观锁设计思想的实现。CAS 的思想是:在“读取 - 修改 - 写回”操作序列中,先读取并修改数据,写回数据前先判断读取数据后的这段时间内

    2024年02月05日
    浏览(42)
  • Java——》CAS

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月05日
    浏览(47)
  • Java多线程系列——CAS机制

    在并发编程的世界里,线程安全是个不得不面对的问题,而CAS(Compare-And-Swap,比较并交换)正是保障并发安全中一种非常关键的机制。本文将深入剖析Java多线程环境下的CAS机制,包括其工作原理、实现方式、面临的问题以及相关的优化策略,力求为读者带来全面的了解。  

    2024年02月22日
    浏览(45)
  • 【Java多线程进阶】CAS机制

    前言 CAS指的是Compare-And-Swap(比较与交换),它是一种多线程同步的技术,常用于实现无锁算法,从而提高多线程程序的性能和扩展性。本篇文章具体讲解如何使用 CAS 的机制以及 CAS 机制带来的问题。 目录 1. 什么是CAS? 2. CAS的应用 2.1 实现原子类 2.2 实现自旋锁 3. CAS的ABA问

    2024年02月10日
    浏览(39)
  • 深入浅出Java多线程(十):CAS

    大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第十篇内容:CAS。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在多线程编程中,对共享资源的安全访问和同步控制是至关重要的。传统的锁机制,如synchronized和ReentrantLock等

    2024年03月11日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包