java中多线程之CAS(compareAndSet),Unsafe类大白话详解.

java中多线程之CAS(compareAndSet),Unsafe类大白话详解

  • 什么是CAS
  • CAS原理
      • Unsafe类:


什么是CAS

比较并交换

在学习CAS之前,我们先了解一下JMM。什么又是JMM?我只知道JVM。这他妈是啥东西啊?

JMM:java内存模型。jmm是一种抽象的概念,并不真实存在,它描述的是一种规范,通过这种规范定义了程序中的各个变量的访问形式。(仔细读,还是能读懂的)

JMM关于同步的规定(仔细读):
1.线程解锁前,必须把共享变量的值刷新回主内存
2.线程加锁前,必须读取主内存的最新值到自己的工作内存
3.加锁解锁是同一把锁

知道看不懂,开始白话文解释!

JVM我们的java虚拟机运行程序的时候,是以线程为最小刻度的。而每个线程创建的时候,jvm就会为这个线程创建一个工作内存,该工作内存是私有的,只能被当前线程所访问。
而JMM内存模型中规定:所有的变量都储存在主内存中,所有线程都能访问,但线程对变量的任何操作(读取赋值等)都必须在工作内存中进行,首先要将主内存中的变量拷贝到自己的工作内存中,然后才能对变量进行操作,操作完成后再讲变量写会主内存中。

看不懂没事,先上一段代码

    public static void main(String[] args) {
        AtomicInteger atomicInteger  = new AtomicInteger(5);
        boolean success = atomicInteger.compareAndSet(5,10);
        System.out.println(success);
        System.out.println(atomicInteger.get());
    }
  1. 声明一个原子类AtomicInteger实例,初始化值为5
  2. 实例对象atomicInteger 调用方法compareAndSet,也即是CAS方法。
  3. 传了两个参数,方法返回一个布尔值
  4. 打印布尔值
  5. 打印atomicInteger的值。

那么这段代码是什么意思呢?compareAndSet方法是干啥的,其中的两个参数是干嘛的?

compareAndSet两个参数:

  1. 期望值
  2. 更新值

还是看不懂,好,我们梳理一下程序进来的时候发生了什么事情。

  1. 现在有A,B两个线程同时进到这段代码
  2. 根据JMM内存模型,AB两个线程分别拷贝了变量atomicInteger 到自己的工作内存中,值都为5
  3. 现在A线程跑得快一点走到了compareAndSet(5,10)方法。
  4. compareAndSet(5,10)表达的意思是:当前线程去主内存中查看atomicInteger的值,如果为5(第一个参数),好,那我就把主内存的atomicInteger 的值改为10(第二个参数)。方法执行成功,返回True
  5. 这个时候B线程开始执行compareAndSet(5,10)方法。
  6. 重复第4步的内存判断
  7. 由于A线程跑得快,主内存的值已经从5改到了10,但B线程执行的依然是compareAndSet(5,10):我是B线程,我想把atomicInteger 的值从5改到10,但我在执行操作的时候不希望有其他线程在我前面操作,我也不能在覆盖其他线程已经完成的操作。我在操作之前得知atomicInteger 的初始值是5,那我现在在进行本次更新操作的时候atomicInteger 的值必须还是5(期望值),如果不是5,说明有其他线程进行了操作,那我就不操作了!方法返回False

CAS原理

atomicInteger.getAndIncrement()

上面这行代码就是和我们平时的i++一样
我们知道i++在底层是线程不安全的,它被分为了3步

  1. 获取主内存的当前值到自己的工作内存
  2. +1操作
  3. 把最新值写回到主内存

那为什么atomicInteger.getAndIncrement()就可以实现线程 安全呢?

我们点进去看一下getAndIncrement方法:

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

这里我们看到是调用unsafe类中的getAndAddInt方法,并传了3个参数:

  1. this:当前对象
  2. valueOffset:内存偏移量
  3. “1”:写死的值,代表加1的操作

这里引出两个问题:什么是unsafe类?什么是内存偏移量?
我们继续往下看,最重要的来了。

先看看unsafe和valueOffset在AtomicInteger 中的声明:

java中多线程之CAS(compareAndSet),Unsafe类大白话详解._第1张图片

Unsafe类:

Unsafe类是CAS的核心类,jdk自带。由于java方法无法直接访问底层系统,需要通过本地方法(native修饰)来方法,Unsafe相当于一个后门,该类的所有方法都是native修饰,可以直接获取到特定内存中的数据。

valueOffset内存偏移量就相当于C中的指针,Unsafe类通过valueOffset去定位数据在内存中的位置从而获得数据

这个时候我们再看这个方法:

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

意思就很明确了:我要拿到当前对象(atomicInteger )的内存地址值(valueOffset),然后进行内存意义上的+1操作!

好!我们现在进去unsafe类看看getAndAddInt这个方法是什么样的:

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

三个参数:

  1. var1:传进来的对象,这里是atomicInteger
  2. var2:当前对象的内存偏移量
  3. var4: 1

运行步骤:

  1. 声明变量var5
  2. this.getIntVolatile(var1, var2):通过var1(atomicInteger )对象和 var2 (当前对象的内存偏移量),获得对象当前的值!(假设当前是5)
  3. this.compareAndSwapInt(var1, var2, var5, var5 + var4) :如果当前这个对象var1中,var2上的内存地址值(内存中的最新值,可能被其他线程改掉了)和var5(期望值)相比一样。那我就进行+1操作:var5 + var4。方法返回true。while(!true)取反为false。跳出循环,返回+1后的值var5.
  4. 如果步骤3中compareAndSwapInt方法返回FALSE,表明当前值和期望值不一样,说明内存中的最新值已经其他线程改掉了!那我就继续循环,获取最新值,再来比较一次

CAS缺点,以及ABA问题和解决办法.

好了 基本已经讲完,欢迎大家评论区指出不足,一起学习进步!

大家看完了点个赞,码字不容易啊。。。

你可能感兴趣的:(java基础,java,多线程,jvm)