什么是CAS操作

CAS操作

之前说在java.util.concurrent.atomic包下提供的原子操作类底层使用的是CAS,那么什么是CAS呢,CAS的全称为Compare And Swap,比较并替换,CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B

在进行变量更新时,需要对比预期值A和内存地址V中的实际值,如果两者相同,才会将V对应的的值改为B

AtomicInteger为例

java 复制代码
// unsafe提供了硬件级别的原子操作
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 存储value在AtomicInteger中的偏移量
private static final long valueOffset;
// 存储AtomicInteger的int值,该属性需要借助volatile关键字保证其在线程间是可见的
private volatile int value;

static {
  try {
    // valueOffset表示的是AtomicInteger对象value成员变量在内存中的偏移量,可以当做是value变量的内存地址
    valueOffset = unsafe.objectFieldOffset
      (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}

public final boolean compareAndSet(int expect, int update) {
  // this为当前对象,valueOffset参数代表了V对象中的变量的偏移量,expect参数代表了A变量预期值,update参数代表了B新的值
  // 如果对象this中内存偏移量为valueOffset的变量值为expect,则使用新的值update替换旧的值expect
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

Unsafe类

CAS的底层使用的是Unsafe类,Unsafe类中的方法都是native方法,看下提供了哪些方法

java 复制代码
// 返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中指定字段时使用
public native long objectFieldOffset(Field field);
// 获取数组中第一个元素的地址
public native int arrayBaseOffset(Class<?> arrayClass);
// 获取数组中一个元素占用的字节
public native int arrayIndexScale(Class<?> arrayClass);
// 比较对象obj中偏移量为offset的变量值是否与except相等,相等则使用update替换旧的except
public final native boolean compareAndSwapInt(Object obj, long offset, int except, int update);
// 获取obj对象中偏移量为offset的变量对应volatile语义的值
public native Object getObjectVolatile(Object obj, long offset);
// 设置obj对象中offset偏移的类型为Object的field的值为value
public native void putObjectVolatile(Object obj, long offset, Object value);
// 阻塞当前线程,isAbsolute等于false且time等于0表示一直阻塞;time大于0表示等待指定的time后阻塞线程会被唤醒,这个time为所休眠的时间;如果isAbsolute等于true,且time大于0,表示阻塞的线程到指定的时间点后会被唤醒,这个time为毫秒级的时间戳
// 如果其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时,或者其他线程调用了unPark方法并且把当前线程作为参数时,当前线程都会返回
public native void park(boolean isAbsolute, long time);
// 唤醒调用park后阻塞的线程
public native void unpark(Object thread);

Unsafe类不可以直接使用,因为Unsafe会判断当前的类加载器是不是Bootstrap类加载器,如果不是的话,会抛出异常SecurityException

java 复制代码
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

既然不能直接使用,那么只能使用反射来使用Unsafe类了

java 复制代码
public class TestUnsafe {
    static final Unsafe unsafe;

    static final long valueOffset;

    private volatile int value = 0;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true);

            unsafe = (Unsafe) theUnsafe.get(null);

            valueOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("value"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("");
        }

    }

    public static void main(String[] args) {
        TestUnsafe testUnsafe = new TestUnsafe();

        unsafe.getAndAddInt(testUnsafe, valueOffset, 1);
        System.out.println(testUnsafe.value);
    }
}

CAS的缺陷

虽然CAS采用的无锁操作来提供性能,但是CAS并不是完美的,存在了很多的不足

  • CPU开销大,因为在高并发的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,一直循环下去,会给CPU带来很大的压力
  • 不能保证代码块的原子性,CAS只能保证一个变量的原子性操作,而不能保证整个代码块的原子性
  • ABA问题,由于CAS对比的是两个最终值,所以可能会导致中间过程中值变化无法感知,变量的值从A变成B,然后再从B变成A,构成了环形转换

ABA问题的解决

解决这个问题很简单,ABA的本质就是无法感知中间过程,那么加一个版本号就可以了,每次版本号递增1,在比较时,不仅要比较内存地址V和旧的预期值A,还要比较一下版本号

在JDK中AtomicStampedReference类给每个变量都配备了一个时间戳stamp,从而避免了ABA问题的产生

java 复制代码
int stamp = 1;
// 使用stamp来作为初始时间戳,为数据增加一个版本号,在修改数据的时候除了提供旧值之外,还要提供旧的版本号
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(0,stamp);
// 预期值,更新后的值,预期版本,更新后的版本 
stampedReference.compareAndSet(0,1,stamp,stamp+1);

zhhll.icu/2021/多线程/基础...

本文由mdnice多平台发布

相关推荐
2401_8574396920 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66621 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索23 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨29 分钟前
Filter和Listener
java·filter
qq_49244844633 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国42 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
找了一圈尾巴1 小时前
前后端交互通用排序策略
java·交互
哎呦没4 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957586 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功8 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python