赞
踩
CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用。
简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。
CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。
优点:
缺点:
CAS存在ABA问题:
当前线程只能感知共享变量的值有没有改变,假设别人从A改成了B,又改成了A,则当前线程感知不到,即ABA问题
可以使用版本号机制解决ABA问题,如原子类AtomicStampedReference,加入了版本号,每当修改值,版本号就会+1
如 AtomicStampedReference 加入了版本号,每当修改值,版本号就会+1。这样根据版本号就知道有没有别的线程修改过,而且还能知道修改过几次
再如 AtomicMarkableReference,他的版本号是一个boolean类型,所以只能判断是否修改过值,不知道修改过几次。
AtomicMarkableReference只能缓解ABA问题,如果别人修改标记后,又改回来,则无法感知。
CAS 机制可以高效地实现原子操作,但仍不完美:
Java的JUC包里有没有现成的类可以解决CAS的ABA问题?
/** * @description: 解决 CAS 带来的 ABA问题 */ public class ABADemo2 { //6为传入的值,1为版本号 static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(6, 1); public static void main(String[] args) { //使用版本号机制来验证ABA问题 new Thread(() -> { //获取当前版本号 int stamp = atomic.getStamp(); System.out.println("线程A1的版本号为:" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //如果当前引用 等于 预期值并且 当前版本戳等于预期版本戳, 将更新新的引用和新的版本戳到内存 //compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp); atomic.compareAndSet(6, 10, atomic.getStamp(), atomic.getStamp() + 1); System.out.println("线程A2的版本号为:" + atomic.getStamp()); System.out.println(atomic.compareAndSet(10, 6, atomic.getStamp(), atomic.getStamp() + 1)); System.out.println("线程A3的版本号为:" + atomic.getStamp()); }, "线程A:").start(); //使用【乐观锁】思想解决ABA问题 new Thread(() -> { int stamp = atomic.getStamp(); System.out.println("线程B1的版本号为:" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomic.compareAndSet(6, 2, stamp, stamp + 1)); System.out.println("线程B2的版本号为:" + atomic.getStamp()); }, "线程B:").start(); } }
为什么会导致ABA问题?
这是因为 CAS 算法是在某一时刻取出内存值然后在当前的时刻进行比较,中间存在一个时间差,在这个时间差里就可能会产生 ABA 问题。
Java提供的API中使用CAS的地方有很多,比较典型的使用场景有原子类、AQS、并发容器。
CAS的实现离不开操作系统原子指令的支持,Java中对原子指令封装的方法集中在Unsafe类中,包括:原子替换引用类型、原子替换int型整数、原子替换long型整数。这些方法都有四个参数:var1、var2、var4、var5,其中var1代表要操作的对象,var2代表要替换的成员变量,var4代表期望的值,var5代表更新的值。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。