理解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就不再去对已经修改过的值进行操作了
相关推荐
2401_853275733 分钟前
ArrayList 源码分析
java·开发语言
zyx没烦恼3 分钟前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb36363636363 分钟前
整数储存形式(c基础)
c语言·开发语言
feifeikon6 分钟前
Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
开发语言·python
爪哇学长7 分钟前
SQL 注入详解:原理、危害与防范措施
xml·java·数据库·sql·oracle
大鲤余13 分钟前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust
浪里个浪的102415 分钟前
【C语言】从3x5矩阵计算前三行平均值并扩展到4x5矩阵
c语言·开发语言·矩阵
MoFe122 分钟前
【.net core】【sqlsugar】字符串拼接+内容去重
java·开发语言·.netcore
_江南一点雨30 分钟前
SpringBoot 3.3.5 试用CRaC,启动速度提升3到10倍
java·spring boot·后端
Envyᥫᩣ35 分钟前
深入浅出C#编程语言
开发语言·c#