理解CAS

理解CAS

什么是CAS

Compare And Swap 比较并交换

JUC 有两大核心:CAS 和 AQS

  • CAS是java.concurrent.automic包的基础
  • AQS是java.concurrent.locks包的基础
  • CAS是一条并发原语
  • 判断值是否到达预期值,如果到达了预期值,就更新
    • 如果预期值是5,更新值是9,那么如果该数据到达了5,就更新为9
  • 过程是原子性的
  • 调用sun.msic.Unsafe类中的方法
  • 因为java是没有办法操作硬件,但是c++可以操作硬件,于是通过Unsafe这个类中的元素操作C++进而操作硬件

为什么会出现CAS

说明:

  • 线程

因为,线程的创建与销毁是非常消耗时间的,而线程的执行时间是非常短的,所以出现了线程池,只需要创建一次线程,下次使用的时候,就可以直接使用线程池中的线程了

  • synchronized锁

之前就谈论过,表示synchronized是不太友好的操作,在执行到锁时,会经历用户态与内核态的转变,非常耗时,而synchronized是一个很小的区域,就是细粒度很高的操作,而为了这一点点的操作去耗时,是非常不划算的

因此出现了CAS

通过AtomicInteger介绍CAS

自己的方法调用

java 复制代码
atomicInteger.getAndIncrement();

AtomicInteger类

java 复制代码
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}


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

通过unsafe类获取地址偏移量,但是具体是如何获得的,应该是在c++中操作的,在java中是没有写的

Unsafe类

java 复制代码
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;
}
  • var 5 是通过AtomicInteger类和地址偏移量获得的值

  • 循环比较,通过地址(var1 ,var2)得到的值与car是否相等,如果相等就让,var5 = var5 + var4

  • var4 = 1;是传入的数值

  • 采用的叫自旋锁

  • 因为是操作内存的,所以调度是非常快的

上述最后是调用的compareAndSwapInt()方法

AtomicInteger自己也对上面的方法进行了封装

java 复制代码
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

我们也可以直接调用这个类

缺点:

1、循环耗时

2、一次性只能保证一个共享变量的原子性

3、会产生ABA问题

CAS:ABA问题

什么是ABA问题

就是线程A,先获得了number的值 = 1,然后准备操作,但是没有操作 ,

线程B ,也拿到了number的值,然后将值 = 3 ,然后再次操作, number = 1,改回去了

但是线程A却什么都不知道

解决CAS问题

使用AtomicStampedReference,含有时间戳的类,也可以叫版本号

每次修改都需要对版本 + 1,这样就可以防治ABA问题

java 复制代码
public class CasTest {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程a将值改变了");
            System.out.println(atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("线程a将值改回来了");
            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
        },"a").start();


        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 5
                    , stamp, stamp + 1));
        },"b").start();
    }
}
  • 线程B先拿到了版本,但是没有操作,延迟了2秒
  • 线程B在这期间,时间为1秒的时候拿到了版本,这个时候,每次执行都+1,这样线程a修改了值后,线程b就不再去对已经修改过的值进行操作了
相关推荐
半盏茶香1 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
Evand J2 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB2 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
Ronin3052 小时前
11.vector的介绍及模拟实现
开发语言·c++
计算机学长大白3 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
suweijie7683 小时前
SpringCloudAlibaba | Sentinel从基础到进阶
java·大数据·sentinel
公贵买其鹿4 小时前
List深拷贝后,数据还是被串改
java
PieroPc4 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439697 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna7 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos