一文理解CAS和自旋的区别

2023年 12月 28日 55.2k 0

我们在面试的时候,有时候在学习的时候,经常性的会遇到一些关于锁的问题,尤其是面试官会提出提问,你对锁了解的多么?你知道锁的原理么?等等问题,于是也就会有后续延伸出来的,你知道 CAS 么?你知道什么是自旋么?

自旋

顾名思义,自旋可以理解为“自我旋转”,放到程序中就是"自我循环",比如while循环或者for循环。结合着锁来理解的话就是,先获取一次锁,如果获取不到锁,会不停的循环获取,直到获取到。不像普通的锁那样,如果获取不到锁就进入阻塞状态。

CAS

CAS 是什么,它的英文全称是 Compare-And-Swap,中文叫做“比较并交换”,它是一种思想、一种算法。

CAS算法有3个基本操作数:

  • 内存地址V
  • 旧的预期值A
  • 要修改的新值B

在并发场景下,各个代码的执行顺序不能确定,为了保证并发安全,我们可以使用普通的互斥锁,比如Java的 synchronized, ReentrantLock等。而CAS的特点是避免使用互斥锁,当多个线程并发使用CAS更新同一个变量时,只有一个可以操作成功,其他都会失败。而且用CAS更新失败的线程并不会阻塞,会快速失败并返回一个失败的状态,允许你再次尝试。

而Compare-And-Swap(CAS)是一种原子操作,用于实现多线程环境下的同步和并发控制。其基本原理如下:

  • 读取内存值:首先,CAS会读取内存中的一个变量的当前值。
  • 比较内存值和预期值:接下来,CAS会将读取的值与预期值进行比较。如果两者相等,则说明内存中的值没有被其他线程修改。
  • 如果相等,则将新值写入内存:在比较阶段,如果发现内存值与预期值相等,CAS会尝试将新值写入内存中。这个写入操作是原子的,即在这个过程中不会被其他线程中断。
  • 如果写入成功,则操作完成;否则重复上述步骤:如果写入操作成功,CAS完成。如果写入操作失败,说明在比较和写入的过程中,内存值已经被其他线程修改,此时需要重新执行整个CAS操作。
  • CAS的基本原理就是利用比较和写入的原子性操作来实现对共享变量的原子操作,从而避免了传统锁机制中的死锁和线程阻塞问题。

    自旋锁和CAS的关系是什么呢?

    其实他们是两个不同的概念 自旋是一种锁优化的机制,在锁优化中『自旋锁』指线程空转重试获取锁,避免线程上下文切换带来的开销。

    CAS是一种乐观锁机制,cas是通过比较并交换,失败的时候可以直接返回false不用自旋的获取。只是一般应用场景下,cas都会带有重试机制(while或者for实现空转,不断尝试获取)。

    如果硬有关系,那么可以这样理解

    自旋锁 = 循环+CAS

    我们都知道了这个自旋锁和 CAS 的关系了,那么CAS 都有哪些缺点呢?

    Compare-And-Swap (CAS) 的缺点包括:

  • 自旋等待:CAS 在执行时会进行自旋等待,如果失败则需要重试,这会消耗处理器资源。
  • ABA 问题:CAS 只能检测到共享变量的值是否发生了变化,但无法检测到变量的值是否经历了类似 A->B->A 的变化,这可能导致一些意外的问题。
  • 无法保证公平性:CAS 操作是非阻塞的,因此无法保证等待线程的公平性,可能导致某些线程长时间无法获得执行机会。
  • 无法解决死锁:CAS 无法解决死锁问题,如果多个线程同时执行 CAS 操作,可能导致死锁的发生。
  • 限制性:CAS 操作通常只能应用于单个变量,对于复杂的数据结构,需要额外的处理来实现原子操作。
  • 总的来说,CAS 虽然具有高效的特点,但也存在着一些局限性和缺点。

    既然我们说了这个 CAS 那么面试官不可避免的就会问到,既然你了解了 CAS ,那么你是不是也对 ABA 问题有了解呢?

    什么是 ABA 问题

    我们先来看什么是 ABA 的问题。

    ABA问题是在分布式系统中常见的一种数据一致性问题。它的名称来源于三个操作:A(原始值)、B(第一个读取)、A(第二个读取)。ABA问题发生在一个线程T1读取了一个共享变量的值A,然后另一个线程T2修改了这个共享变量的值为B,然后又改回A,最后线程T1再次读取这个共享变量的值,发现仍然是A。在这种情况下,线程T1可能会错误地认为共享变量的值没有改变,从而导致数据不一致。

    解决ABA问题的常见方案是使用版本号或者标记来跟踪数据的变化。通过在每次数据变化时增加版本号或者标记,可以避免ABA问题的发生。另外,使用CAS(Compare and Swap)操作也可以解决ABA问题,CAS操作会在更新变量时检查变量的值是否仍然是预期值,从而避免了ABA问题的发生。

    简单的说就是

    比如线程1从内存位置V中取出A,此时线程2也取出A。且线程2做了一次cas将值改为了B,然后又做了一次cas将值改回了A。此时线程1做cas发现内存中还是A,则线程1操作成功。这个时候实际上A值已经被其他线程改变过,这与设计思想是不符合的。

    那么这个问题出现在哪里呢?

    • 如果只在乎结果,ABA不介意B的存在, 没什么问题
    • 如果B的存在会造成影响,需要通过 AtomicStampReference,加时间戳解 决。

    那关于自旋和 CAS  你了解了么?

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论