主页

索引

模块索引

搜索页面

CAS 机制

  • Compare and Swap

CAS 机制是一种数据更新的方式。

悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized 就是 java 中悲观锁的典型实现,synchronized 使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和 Runnable 状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用 synchronized 将其他线程都锁住了,线程从 Blocked 状态切换回 Runnable 花的时间可能比你的更新操作的时间还要长。

乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS 机制就是乐观锁的典型实现。

这个机制中有三个核心的参数:

1. 主内存中存放的共享变量的值:V(一般情况下这个 V 是内存的地址值,通过这个地址可以获得内存中的值)
2. 工作内存中共享变量的副本值,也叫预期值:A
3. 需要将共享变量更新到的最新值:B
https://img.zhaoweiguo.com/knowledge/images/architectures/distributes/CAS1.jpg

如上图中,主存中保存 V 值,线程中要使用 V 值要先从主存中读取 V 值到线程的工作内存 A 中,然后计算后变成 B 值,最后再把 B 值写回到内存 V 值中。多个线程共用 V 值都是如此操作。CAS 的核心是在将 B 值写入到 V 之前要比较 A 值和 V 值是否相同,如果不相同证明此时 V 值已经被其他线程改变,重新将 V 值赋给 A,并重新计算得到 B,如果相同,则将 B 值赋给 V。

备注

CAS 机制中的这一步骤是原子性的(从指令层面提供的原子操作),所以 CAS 机制可以解决多线程并发编程对共享变量读写的原子性问题。

优点

  • 可以保证变量操作的原子性

  • 并发量不是很高的情况下,使用 CAS 机制比使用锁机制效率更高

  • 在线程对共享资源占用时间较短的情况下,使用 CAS 机制效率也会较高

CAS 存在的问题

  1. ABA 问题:

    CAS 在操作的时候会检查变量的值是否被更改过,如果没有则更新值,
    但是带来一个问题,最开始的值是 A,接着变成 B,最后又变成了 A。
    经过检查这个值确实没有修改过,因为最后的值还是 A,但是实际上这个值确实已经被修改过了。
    为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值,
    一个版本号和某个值,A——>B——>A 问题就变成了 1A——>2B——>3A
    
  2. 循环时间长开销大:

    在线程之间竞争程度大的时候,如果使用 CAS,每次都有很多的线程在竞争,
    也就是说 CAS 机制不能更新成功。
    这种情况下 CAS 机制会一直重试,这样就会比较耗费 CPU。
    因此:
    如果线程之间竞争程度小,使用 CAS 是一个很好的选择;
    如果竞争很大,使用锁可能是个更好的选择
    
  3. 只能保证一个共享变量的原子操作:

    Java 中的 CAS 机制只能保证共享变量操作的原子性,而不能保证代码块的原子性
    

主页

索引

模块索引

搜索页面