java CAS详解

什么是CAS(Compare and swap - 比较并交换)

CAS的全称为 compare and swap 它是一条CPU 并发原语

什么叫做CPU并发原语

原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU原子指令,不会造成所谓的数据不一致问题

CAS的功能是什么

判断内存某个位置的值是否为预期值,如果是 则更改为新的值,这个过程是原子的 是通过unsafe类来保证原子性

CAS的底层原理是什么, 为什么可以保证原子性

  1. 自旋锁
  2. unsafe类

什么是UnSafe类,UnSafe类的作用是什么

是什么:是CAS的核心类,Unsafe相当于一个后门,基于该类可以直接操作操作系统中特定内存的数据。UnSafe类存在于 sun.misc 包中,

为什么出现:java方法无法直接访问底层系统(只要是native修饰的方法,java都无能为力)

作用:通过UnSafe类操作操作系统中特定内存的数据 (其内部方法操作可以像 C 的指针一样直接操作内存)

因为 java 中 CAS 操作的执行依赖于 UnSafe 类的方法

UnSafe类有什么特点

UnSafe类中的所有方法都是native修饰的,也就是说 Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

使用atomic包中AtomicInteger下的 2个方法理解CAS原理

1、详解:AtomicInteger中的 compareAndSet ( int expect, int update ) 方法

现在有A,B,C 个线程操作主物理内存中的共享变量,当 A 线程操作这个共享变量的时候,

  1. 情况1:对 A 来说最开心的事情就是这个共享变量没有被 B, C 线程修改过,可以直接将在自己工作内存中操作后的值替换掉主内存中的值
  2. 情况2:对 A 线程不开心的事情是 B,C 线程 已经把主内存中的共享变量修改了,那么此时 A 线程发现共享变量的值和我预期的初始值不同,就不能修改共享变量中的值了

compareAndSet实现了上面的2中情况,返回的是 boolean类型,

参数意义:

  1. expect是我预期的值(共享变量的原始值,A的工作线程中没有操作时的值)
  2. update 如果共享变量中的值没有被 B,C线程修改,那么就将共享变量中的值替换成 update这个值

方法执行情况:

1. 如果expect和我预料中的一样,那么 compareAndSet操作`执行成功`,主内存中最终的值 是这个update
2. 如果expect和无预料中的不一样,那么 compareAndSet 操作`不会执行`,主内存中的值是其他线程修改后的那个值
public class CASTest {
    public static void main(String[] args) {
      //主内存中 atomicInteger 的初始值为 5
        AtomicInteger atomicInteger = new AtomicInteger(5);
      // 如果初始值是5,那么将初始值修改为1024,然后得修改后的值
        System.out.println(atomicInteger.compareAndSet(5, 1024) + "主内存中的最终值为: " + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2048) + "主内存中的最终值为: " + atomicInteger.get());
    }
}

可以看到 最终的操作结果
在这里插入图片描述

2、详解AtomicInteger中 getAndIncrement() 方法

在这里插入图片描述
this:当前对象

valueOffSet:当前对象的内存地址偏移量,就是this的内存地址

什么叫做内存偏移量:

类似于在一个班级中,某个同学的座位 有具体的列,具体的行,那么这一列 这一行就一定存在一个人,我不管这个人是谁,我就是可以操作他,UnSafe类可以通过内存偏移地址获取数据 极度精确

这个方法的意思就是:例如:对第三排第四列的这个同学进行 +1 的操作,我不管这个同学是谁,我只管对这个同学进行 + 1的操作

getAndIncrement() 中的 getAndAddInt(Object var1, long var2, int var4) 方法

在这里插入图片描述

  1. 创建一个返回对象
  2. getIntVolatile(var1, var2) 表示的是 this对象在 valueOffset上的值是多少,将这个值赋给var5
    1. var1:this
    2. var2:valueOffset
  3. 情况1. this.compareAndSwapInt(var1, var2, var5, var5 + var4) 表示的是:this对象中 var2 地址的值 和 var5相同的话 就将var5 + 1
  4. 取反是因为 如果修改成功的话 compareAndSwapInt方法就会返回 true 取反 为false 就跳出循环
  5. 情况2. 如果valueOffSet的地址值不是最新的,被其他线程修改过,那么就再取一次这个地址的最新的值,和var2 进行比较,如果不相等,就一直取,直到取到了最新的值,然后将这个值进行修改操作

生活中的例子:

在一个鸡蛋槽中有一个纸条 A ,现在多个人来操作这张纸条,取出这个鸡蛋槽中 第5个槽位()的纸条,将纸条用复印机复印一份,自己修改后,在将自己修改后的纸条替换上去,每个人都执行相同的操作 ------------ 前提条件:只有自己的复印的那一份和槽位中的纸条完全一致才可以进行替换 (先判断是否一致,然后执行替换操作)

情况1:线程 A 复印的和槽位中的一致:直接进行替换操作

情况2:线程 A 复印的和槽位中的不一致,那么就将这一份新的先复印一份,然后判断这份新的纸条和槽位中的纸条是否一致(多个线程修改,可能还是不一致),突然有一次,发现复印的纸条和槽位上的纸条一致,那么 就将自己修改好的纸条替换成槽位上的纸条

CAS的缺点是什么

  1. getAndInt方法中 如果比较的时候2个值一直不相等的话,就会给cpu带来很大的开销 — 上述例子中,如果我取鸡蛋槽中的纸条的时候,每次都和我复印的那个值不同 就会一直循环这个操作
  2. 只能保证一个共享变量的原子性 – 上述例子中 鸡蛋槽中每次只能盛放一个共享变量
  3. ABA问题

什么是ABA问题

现象:

背景:2个线程 A, B 操作内存中的共享变量 val,A操作这个 val 花费10s中,B操作这个 val 花费2s中,现在val的初始值是1A和B同时取出共享变量中的值

1、首先B线程 将这个1先修改成2,然后又将2修改回1,因为B线程的执行速度要快

2、然后A线程执行,发现共享变量中的数据和我取出时的数据一致,然后 A 线程将这个val 修改成了5

整体看下来,对A线程来说,共享变量中的值 看似没有变化,其实已经换了好几次了

举个生活中的例子:

A,B 2个人共同解一道数学题,A解这一题 花费10s中,B解这一题 花费2s中

1、首先 B 来操作这一题,然后将答案写在了试卷上,看了看答案,发现不对,然后有把刚刚写的答案删了,现在试卷上还是一片空白

2、A 发现 哎呦 B 线程还没有把这一题解出来呢,就自己把正确的答案写在了试卷上

虽然对B来说这一题并没有解出来,虽然最终的结果是 A解出来了这一题,但是试卷上是有 B 操作过的痕迹的

如果我不在意其中B线程是否修改过这个共享变量,那么就没问题,但是如果我介意这个值被修改过,那就不行

怎样解决ABA问题

使用时间戳原子引用

什么是原子引用
用法和 AtomicInteger都一样
AtomicInteger -- 对应的是 AtomicReference<Integer>

原子引用可以包装

什么是时间戳原子引用

思想:在修改的过程中 添加一个版本号,类似于git ,如果先修改为 A 然后 B 然后 A 虽然最终的值没有变化,但是我的版本号变了

代码示例:

public class AtomicStampedReferenceTest {

    /**
     * 加 static 是在main方法中使用
     */
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(5);
    /**
     * 初始值为5 初始版本为1
     */
    static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(5, 1);

    public static void main(String[] args) {
        //aa 线程实现 ABA问题
        new Thread(() -> {
            atomicReference.compareAndSet(5, 10);
            atomicReference.compareAndSet(10, 5);
        }, "aa").start();

        //bb 线程对这个值进行修改操作
        new Thread(() -> {
            //等待1s种 是为了让aa 线程执行完成
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            //对共享变量进行修改
            System.out.println(atomicReference.compareAndSet(5, 100) + ", atomicReference中的值为" + atomicReference.get());
        }, "bb").start();


        //上面是 ABA问题的产生###############3###############3###############3###############3###############3###############3
        // 下面是ABA问题的解决, 使用时间戳原子引用
        new Thread(() ->{
            // 等2s 是上面的ABA 都执行完了 执行后续操作
            try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(5,10,1,2);
            stampedReference.compareAndSet(10,5,2,3);
        }, "cc").start();

        new Thread(() ->{
            //等3s 使用 stampedReference演示原子引用操作
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(stampedReference.compareAndSet(5, 100, 1, 2)
                    + ", stampedReference的值为:" + stampedReference.getReference()
                    + ", stampedReference的版本号为:" + stampedReference.getStamp()
            );

        }, "dd").start();
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值