什么是CAS?

深入理解Java并发编程(四):CAS操作以及jdk1.8后的优化
参考URL: https://juejin.im/post/6850418111330877454

CAS 是 compare and swap 的缩写,即我们所说的比较交换。cas 是一种基于锁的操作,而且是乐观锁。

在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。java.util.concurrent.atomic 包下的类大多是使用 CAS 操作来实现的(AtomicInteger,AtomicBoolean,AtomicLong)。

CAS操作离不开这三个值(V, O, N):

  • V:内存地址存放的实际值
  • O:旧值
  • N:即将更新的新值

当且仅当VO相同时,即 旧值和内存中实际存放的值相同,这表明该值没有被其他线程更改过,此时CAS通过原子的方式将N赋给V,并返回true。 这是一个比较+更新操作,是原子操作。如果VO不相同,则该值已经被其他线程修改,不能把N赋给V,此时不进行操作,返回false。多个线程使用CAS操作一个变量时,只有一个线程会成功,并且成功更新,其余会失败(并不会阻塞其他线程)。失败的线程会重新尝试,也可以选择挂起线程。

synchronized存在线程竞争的情况下会出现线程阻塞和唤醒锁带来的性能问题,因为这是一种互斥同步(阻塞同步)。而CAS在竞争时如果失败,会进行一定的尝试,而并不是单纯的进行挂起唤醒操作,因此也叫非阻塞同步。

CAS 的问题

(1)CAS 容易造成 ABA 问题
一个线程 a 将数值改成了 b,接着又改成了 a,此时 CAS 认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次version 加 1。在 java5 中,已经提供了 AtomicStampedReference 来解决问题。

(2)不能保证代码块的原子性
CAS 机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证 3 个变量共同进行原子性的更新,就不得不使用 synchronized 了。

(3)CAS 造成 CPU 利用率增加
之前说过了 CAS 里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu资源会一直被占用。

线程 B 怎么知道线程 A 修改了变量

(1)volatile 修饰变量
(2)synchronized 修饰修改变量的方法
(3)wait/notify
(4)while 轮询

synchronized、volatile、CAS 比较

(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
(3)CAS 是基于冲突检测的乐观锁(非阻塞)

02-25 06:34