Java中CAS原理分析

CAS是什么?

在java中锁可以分为悲观锁和乐观锁,今天介绍的CAS就是属于乐观锁的一种实现方式。CAS全称为 Compare And Swap 即比较和交换。乐观锁的乐观的意思就是我认为自己肯定会拿到锁资源,如果拿不到我就一直尝试,直到拿到锁资源为止。

接下来以AtomicInteger原子类来详细解释说明:

private static int threadNum = 100;
private static int numberA = 0;
private static AtomicInteger numberB = new AtomicInteger(0);
private static final ExecutorService executorService = Executors.newCachedThreadPool();
public static void main(String[] args) {
  for (int i = 0; i < threadNum; i++) {
    executorService.execute(() -> {
      numberA += 1;
      numberB.getAndIncrement();
  });
}
System.out.println(numberA); // 95
System.out.println(numberB); // 100
}

通过上面代码可以看出在多线程场景下对AtomicInteger操作是线程安全的而对numberA变量的操作是线程不安全的。

首先进入getAndIncrement()方法

public final int getAndIncrement() {
  // 使用unsafe对象的getAndAddInt方法
  return unsafe.getAndAddInt(this, valueOffset, 1);
}

查看unsafe对象的类:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
  try {
    // 获取value字段相对于当前对象的的offset(起始地址)
    valueOffset = unsafe.objectFieldOffset
    (AtomicInteger.class.getDeclaredField("value"));
  } catch (Exception ex) { throw new Error(ex); }
}

这里的静态代码块AtomicInteger对象初始化之前就执行了,获取AtomicInteger对象value字段相对AtomicInteger对象的”起始地址”的偏移量,Java对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding),”起始地址”的偏移量即是对象头的偏移量。

紧接着继续看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;
}

于上述代码var1,var2等变量不见名知意,转化如下:

public final int getAndAddInt(Object obj, long valueOffset, int delta) {
    int expect;
    do {
        // 先获取内存offset出的value值
        expect = this.getIntVolatile(obj, valueOffset);
        // 然后通过compareAndSwapInt比较并且交换
        // delta为当前value需要加上的数值
        // update = expect + delta
    } while(!this.compareAndSwapInt(obj, valueOffset expect, update));
    return expect;
}

上述代码流程为:

  1. 先获取出当前对象obj(即AtomicInteger对象)在内存中valueOffset处的值
  2. 再通过compareAndSwapInt方法比较获取到的期望值expect与对象obj内的value值是否相等,若相等则更新成update值,否则再次进入循环,直到更新成功为止。

执行流程如下图:

在这里插入图片描述
上面的步骤可以分为三步:

  1. 读取, 从内存中获取对象valueOffset处的value值
  2. 比较
  3. 修改

其中比较和修改要保证原子性

CAS底层靠调用CPU指令集cmpxchg来完成原子操作的。但在多核场景下该指令也不能保证原子性。需要在前面加上lock指令。lock指令可以保证一个CPU核心在操作期间独占一片内存区域。在多核处理中一般通过总线锁和缓存锁来实现。

  1. 总线锁: 多核处理器,CPU是通过总线访问内存的, 可以通过锁住总线来让其他核心无法访问内存
  2. 缓存锁: 总线锁的代价很大,会导致其他核心无法工作。缓存锁是锁住某部分的内存空间, 当一个CPU核心将内存读取到自己的缓存区后,就锁定对应内存区域,期间其他CPU核心无法操作这块区域。

总结

通过上面的AtomicInteger源码的阅读,我对CAS的理解:

CAS操作是不加锁的, 它每次都假设自己可以执行成功, 然后它就去尝试执行了,成功就返回,否则就一直循环去不断地尝试。这样做比加锁和释放锁的性能是要高的。但是如果一直都是失败的,这样长时间的循环(自旋)会给CPU带来很大的开销。

其次CAS还会有ABA的问题,比如我原来value的值为 A, 期间改成了B,后来有变成了A,这个时候某一个线程去执行CAS的之后就会认为value的值没有更改过。但是实际却变化过了。可以使用AtomicStampedReference类解决ABA问题。

还有就是CAS只能保证一个共享变量的原子操作, 没办法保证一块代码或者一个对象的原子操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java CAS(Compare And Swap,比较并交换)是一种常用于多线程编程的原子操作。其主要作用是在多线程环境下保证变量的原子性和一致性,解决多线程竞争条件下的并发问题。 Java CAS原理是通过比较内存的值与期望值是否相等来确定是否进行交换,其核心思想是利用硬件的原子性操作来实现并发的同步,而不需要使用锁(synchronized)等机制。 具体来说,CAS操作包含三个参数:内存地址、旧的预期值和新的值。 1. 首先,CAS会将当前内存地址的值与旧的预期值进行比较,如果相等,则说明内存的值未被其他线程修改。 2. 然后,CAS会使用新的值来更新内存地址的值,完成交换操作。 3. 最后,CAS会返回旧的预期值,可以通过返回值进行判断操作是否成功。 需要注意的是,CAS是一种乐观的并发控制方式,它不会阻塞线程,而是通过不断重试的方式来保证操作的原子性。如果CAS操作失败,那么线程会重新读取内存的值,并重新尝试进行CAS操作,直到成功为止。 然而,CAS也存在一些问题。首先,CAS需要频繁地读取和写入内存,这对内存带宽的要求较高;其次,由于CAS操作是无锁的,因此存在ABA问题,即在操作过程,如果其他线程修改了预期值两次并恢复为原来的值,CAS操作无法察觉。 总之,Java CAS作为一种基于硬件支持的原子操作,可以在多线程环境下实现高效的同步控制,然而它也需要开发人员自行处理ABA问题以及确保程序的正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值