CAS原理
什么是CAS
CAS是一条CPU并发原语,用于判断内存中某个位置的值是否为预期值,如果是则更改为新的值,如果不是就重新获取这个值再判断修改,这个过程是原子的。
CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
CAS原理
CAS并发原语体现在Java中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现CAS汇编指令。这是一套完全依赖于硬件的功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用语,原语由若干指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被终端,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题
CAS的操作过程
- 在内存地址V当中,存储着值为10的变量。
- 此时线程1想要把变量的的值加一 ,对线程1来说 旧的预期值 A= 10 要修改的新值 B 为 11
- 在线程1 修改A之前 线程2 吧变量修改成11
- 线程1 执行操作之前首先把 旧的预期值A = 10和 内存中的值进行比较发现 A 和内存中存的值不一样,提交失败
- 线程1 重新获取内存中的值 并计算要修改的值 此时对线程1 来说旧的值 A 就为 11 要修改的新值 B 为 12 这个重复尝试的操作为自旋
- 这次没有其他线程改变地址V的值。线程1进行比较,发现A和地址V的实际值是相等的
- 线程1进行交换,把地址V的值替换为B,也就是12.
synchronized属于悲观锁,悲观的认为程序中的并发情况严重,所以严防死守,
CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去重试更新。
CAS的缺点:
1 CPU开销大 : 在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很到的压力。
2 不能保证代码块的原子性: CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronized了。
3 可能会产生ABA问题:
什么是ABA问题:
假设内存中有一个值为A的变量,存储在地址V中。我们想有三个线程尝试使用CAS来修改这个值
- 线程1 将A 变成了B
- 线程2 阻塞了
- 线程3 又把B变成A
- 线程2 恢复了发现内存中的值还是A 就把这个值变成B
怎么解决ABA问题呢 加个版本号
在Java中,AtomicStampedReference 类就实现了用版本号作比较额CAS机制。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查标志stamped是否为预期标志,如果全部一致,则继续。
操作过程
- 假设地址V中存储着变量值A,当前版本号是01。线程1获取了当前值A和版本号01,想要更新为B,但是被阻塞了。
- 这时候,内存地址V中变量发生了多次改变,版本号提升为03,但是变量值仍然是A。
- 随后线程1恢复运行,进行compare操作。经过比较,线程1所获得的值和地址的实际值都是A,但是版本号不相等,所以这一次更新失败。
java语言CAS底层如何实现?
利用unsafe提供的原子性操作方法。