详解线程安全机制—CAS

详解线程安全机制—CAS

一、概述

CAS,全称 Compare-And-Swap,翻译一下就是比较和交换,是一种乐观的并发控制机制。

CAS操作包含三个操作数:

  1. 内存地址(V):需要更新的变量的内存地址。
  2. 预期旧值(A):更新操作开始前的值。
  3. 新值(B):将要更新的目标值。

CAS操作的核心原理是:在更新某个变量的值时,会先比较内存地址(V)中的值是否与预期旧值(A)相等,如果相等,则将其更新为新值B,这个过程是原子的,即不可中断的。

举个例子:

  1. 内存地址(V)中存放值为10的变量。
  2. 线程A要把变量值加1,对于线程A来说,预期旧值(A)等于10,新值(B)等于11。
  3. 在线程A执行之前,此时线程B提前将内存地址(V)中的变量值先更新成了11。
  4. 这时线程A执行CAS,首先会将内存地址(V)预期旧值(A)比较,发现不相等(V = 11,A = 10),提交失败。
  5. 线程A尝试自旋操作:重新获取内存地址(V)的值赋值给预期旧值(A),并重新计算想要修改的值,此时线程A的预期旧值(A)等于11,新值(B)等于12。
  6. 线程A重新提交更新,这时没有其他线程更新内存地址(V)中的值,此时内存地址(V)的值和预期旧值(A)相等,更新成功。

从上面的例子可以看出,CAS过程一定是一个原子操作,否则就没有意义了,那么如何保证CAS的原子性呢?

  1. 保证每次取内存地址(V)的值时,取到的一定是最新的,也就是变量需要使用 volatile 关键字修饰,volatile 保证了变量修改后一定会写回主内存,使用时也一定会从内存读取。
  2. 保证CAS的操作原子性,在Java中,CAS机制是通过sun.misc.Unsafe类中的方法实现的。Unsafe类提供了底层的、不安全的操作,包括对内存的直接访问和修改。Java 的java.util.concurrent.atomic包下的原子类(如AtomicIntegerAtomicLong等)就是基于CAS机制实现的。

使用场景:Android 中协程生命周期作用域 lifecycleScope 就是通过CAS来获取的,源码如下:

public val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = internalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (internalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }
二、CAS的优势与劣势

优势:

  1. 非阻塞:CAS操作是一种非阻塞的算法,它不会使线程进入阻塞状态,因此不会引起线程上下文的切换和调度问题,这在高并发场景下非常有利。而 synchorized 关键字会让没有得到锁资源的线程进入Blocked状态,得到锁资源后恢复为Runnable状态,这个过程涉及到操作系统用户态到内核态的切换,代价比较高(其实 synchorized 后续也新增了自旋,轻量级锁,偏向锁等方案的优化)。
  2. 高性能:由于CAS操作是非阻塞的,且依赖于硬件的原子性保证,因此在很多情况下,CAS比传统锁性能更高。

劣势:

  1. ABA问题:如果一个变量的值原来是1,被线程A读取后,线程B将其改为2,然后又改回1,此时线程A再次进行CAS操作时,会发现变量的值仍然是1,从而错误地认为该值没有被其他线程修改过。这就是所谓的ABA问题。

    表面看没有什么影响,但是如果业务逻辑上是非幂等的,就会出现问题,比如银行业务由于ABA问题造成了多次扣款。

    解决方案:同时维护一个变量的版本号,在Compare阶段不仅比较预期值和此时内存中的值,还比较两个比较变量的版本号是否一致,只有当版本号一致才进行后续操作,这样就解决了ABA问题。

  2. 开销大:在并发量比较高的情况下,如果许多线程反复尝试去更新一个变量,却又一直更新失败,循环往复,导致较大的开销。

  3. 只能保证一个共享变量的原子操作:CAS操作通常只能保证对单个共享变量的原子操作,对于涉及多个共享变量的复合操作,CAS就无能为力了,这时使用 synchorized 更加适合。

三、总结

从思想上来说,synchorized 属于悲观锁,悲观的认为程序中的并发多,所以严防死守,CAS机制属于乐观锁,乐观的认为程序中并发少,所以线程不断的去尝试更新。

  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值