CAS 机制¶
Compare and Swap
CAS 机制是一种数据更新的方式。
悲观锁更新的方式认为:在更新数据的时候大概率会有其他线程去争夺共享资源,所以悲观锁的做法是:第一个获取资源的线程会将资源锁定起来,其他没争夺到资源的线程只能进入阻塞队列,等第一个获取资源的线程释放锁之后,这些线程才能有机会重新争夺资源。synchronized 就是 java 中悲观锁的典型实现,synchronized 使用起来非常简单方便,但是会使没争抢到资源的线程进入阻塞状态,线程在阻塞状态和 Runnable 状态之间切换效率较低(比较慢)。比如你的更新操作其实是非常快的,这种情况下你还用 synchronized 将其他线程都锁住了,线程从 Blocked 状态切换回 Runnable 花的时间可能比你的更新操作的时间还要长。
乐观锁更新方式认为:在更新数据的时候其他线程争抢这个共享变量的概率非常小,所以更新数据的时候不会对共享数据加锁。但是在正式更新数据之前会检查数据是否被其他线程改变过,如果未被其他线程改变过就将共享变量更新成最新值,如果发现共享变量已经被其他线程更新过了,就重试,直到成功为止。CAS 机制就是乐观锁的典型实现。
这个机制中有三个核心的参数:
1. 主内存中存放的共享变量的值:V(一般情况下这个 V 是内存的地址值,通过这个地址可以获得内存中的值)
2. 工作内存中共享变量的副本值,也叫预期值:A
3. 需要将共享变量更新到的最新值:B
备注
CAS 机制中的这一步骤是原子性的(从指令层面提供的原子操作),所以 CAS 机制可以解决多线程并发编程对共享变量读写的原子性问题。
优点¶
可以保证变量操作的原子性
并发量不是很高的情况下,使用 CAS 机制比使用锁机制效率更高
在线程对共享资源占用时间较短的情况下,使用 CAS 机制效率也会较高
CAS 存在的问题¶
ABA 问题:
CAS 在操作的时候会检查变量的值是否被更改过,如果没有则更新值, 但是带来一个问题,最开始的值是 A,接着变成 B,最后又变成了 A。 经过检查这个值确实没有修改过,因为最后的值还是 A,但是实际上这个值确实已经被修改过了。 为了解决这个问题,在每次进行操作的时候加上一个版本号,每次操作的就是两个值, 一个版本号和某个值,A——>B——>A 问题就变成了 1A——>2B——>3A
循环时间长开销大:
在线程之间竞争程度大的时候,如果使用 CAS,每次都有很多的线程在竞争, 也就是说 CAS 机制不能更新成功。 这种情况下 CAS 机制会一直重试,这样就会比较耗费 CPU。 因此: 如果线程之间竞争程度小,使用 CAS 是一个很好的选择; 如果竞争很大,使用锁可能是个更好的选择
只能保证一个共享变量的原子操作:
Java 中的 CAS 机制只能保证共享变量操作的原子性,而不能保证代码块的原子性