1.什么是CAS
CAS 的全称为Compare-And-Swap ,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
就一句话 比较并交换 废话不多说上代码
2.代码Demo验证
static class CASDemo { public static void main(String[] args) throws InterruptedException { //主线程正常执行 compareAndSet比较并交换方法 AtomicInteger atomicInteger = new AtomicInteger(5); boolean b = atomicInteger.compareAndSet(5, 2019); System.out.println(b + "\t current data:" + atomicInteger.get()); //休息3秒 Thread.sleep(3000); //开个子线程取修改主内存变量 看看能否修改成功 compareAndSet比较并交换方法 new Thread(() -> { boolean b1 = atomicInteger.compareAndSet(5, 2018); System.out.println(b1 + "\t current data:" + atomicInteger.get()); }).start(); } }
3.通过流程图解释原理
t1线程的工作内存初始值拷贝主内存为5 ,
调用compareAndSet方法修改工作内存值为2019
(修改之后会回写给主内存,并通知其他内存可见,一般加上volitaile关键字)在回写的时候就会判断主内存的值跟我之前传参数期望的值是否相等,
如果相等 那么好 就会正常回写并覆盖主内存值 ,反之值回写不成功总之用人话说就是:::你期望的的值 与 实际主内存的值(就是线程共享变量初始化放在物理内存的值) 它俩是不是一样 一样就可以修改 不一样就改不了
2.CAS的底层原理?如果你知道谈谈UnSafe类的理解
this: 当前原子类对象 offset:内存偏移量(当前对象内存地址)delta: 每次自增1
在构建原子类对象的时候 已经给对应的值加上volitaile关键字 保证可见性了
// The following contain CAS-based Java implementations used on // platforms not supporting native instructions /** * Atomically adds the given value to the current value of a field * or array element within the given object <code>o</code> * at the given <code>offset</code>. * * @param o object/array to update the field/element in * @param offset field/element offset * @param delta the value to add * @return the previous value * @since 1.8 */ public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
通过 do ..while 循环 至少循环一次 然后比较并交换
3.什么是Unsafe类
是CAS的核心类,由于JAVA方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定的内存数据。Unsafe存在于sun.misc包中,其内部方法可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
4 变量 valueOffset :
表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的
3.CAS缺点
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }
1.如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销
2.只能保证一个共享变量的原子操作
3.引出来ABA问题?
4.ABA问题
ABA:狸猫换太子
CAS算法实现一个重要的前提需要取出内存中某时刻的数据在当下时刻比较并交换,那么在这个时间差 会导致数据的变化
通过原型图举例::
解释:当t2线程执行无数次之后最终写回主内存还是A,t1线程到时间执行发现主内存是A,,符合CAS算法
最后导致数据错误!!!!废话不多说 代码演示ABA出现的问题::
static class ABADemo { static AtomicReference atomicReference = new AtomicReference(100); static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<Integer>(100, 1); public static void main(String[] args) { System.out.println("======以下是ABA问题的产生============"); new Thread(()->{ atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2022)+"\t"+atomicReference.get()); },"t2").start();
控制台输出:======以下是ABA问题的产生============
控制台输出:true 2022
5.解决ABA问题
1.使用 带时间戳的原子引用类
AtomicStampedReference<Object> reference = new AtomicStampedReference<>();废话不多说 上代码::
System.out.println("============以下是ABA问题的解决================="); new Thread(() -> { //初始版本号 int stamp = atomicStampedReference.getStamp(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp); atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp()); }, "t3").start(); new Thread(() -> { //初始版本号 int stamp = atomicStampedReference.getStamp(); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp); boolean b = atomicStampedReference.compareAndSet(100, 2022, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "\t线程修改是否成功" + b + "当前最新实际版本号" + atomicStampedReference.getStamp()); System.out.println("当前最新值" + atomicStampedReference.getReference()); }, "t4").start(); } }
控制台输出:============以下是ABA问题的解决=================
控制台输出:t3 第一次版本号:1
控制台输出:t3 第二次版本号:2
控制台输出:t3 第三次版本号:3
控制台输出:t4 第一次版本号:1
控制台输出:t4 线程修改是否成功false当前最新实际版本号3
控制台输出:当前最新值100
小结: 1 CAS比较并交换方法只能保证数据的稳定性 ,并不能保证原子性,原子性是实质
底层是通过JAVA提供的Unsafe类调用C语言外部的接口,底层是调用的汇编的一个原子指令给数据总线加锁所以线程是安全的的。
2 CAS缺点通过 期望值和实际值是否相等 并不能保证中间有空档期 所以需要通过版本号来控制 JUC提供的AtomicStampedReference 时间戳原子类 保证了没改变一次主内存数据 版本号+1 在修改主内存数据时 除了通过值判断相等外还加上版本号预计值和实际值是否相等 双重控制 解决了ABA问题。
原创不易,有不对的地方希望大家指正 !!!!!!