欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

CAS-原子操作解析

程序员文章站 2022-05-03 18:37:00
...

在多线程条件下(A,B两个线程),假如i = 2,我们想通过i++的方式让 i 的值变为4,可能执行不成功,因为i++不是原子性操作,在底层不是表面上的i++一步完成,而是分成几步来操作,所以两个线程很可能拿到的值都是2,最终结果可能为3,所以在进行这种操作的时候,一般是 加锁来实现,但加锁会使效率低下,因此,从jdk5开始,java提供了java.util.concurrent.atomic包来进行原子性操作
CAS-原子操作解析
而这里面的原子类的方法都差不多,并且Atomic包的原子性操作依靠的是包装类Unsafe
我们以AtomicInteger为例做介绍:

如何进行原子性操作

public class CASDemo { // CAS compareAndSet : 比较并交换!
 public static void main(String[] args) {
  AtomicInteger atomicInteger = new AtomicInteger(2020); 
  // 期望、更新
   //public final boolean compareAndSet(int expect, int update) 
   // 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语! 
   System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
   System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement() 
   System.out.println(atomicInteger.compareAndSet(2020, 2021)); 
   System.out.println(atomicInteger.get());
    } 
}

如代码所示,我们列举几个常用的方法

1.compareAndSet()

AtomicInteger atomicInteger = new AtomicInteger(2020);
atomicInteger.compareAndSet(2020, 2021)

如代码所示,该方法有两个参数,我们进入源码看一下:

 public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

根据源码我们可以知道,第一个参数是期望的值,在new的时候我们传进来的值和该值相比较,如果相等,就更新该原子类的值为第二个参数update的值,**那么该过程是如何实现的呢?**我们继续探究源码:在文章开始就说过,Atomic包的原子性操作依靠的是包装类Unsafe,我们看一下该类都有什么内容(只截取其中一部分):


    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

我们发现该类实现原子操作的方法都加上了native关键字,并且都是compare***的形式,我们将这种原子操作称为CAS 操作,因为java无法直接操作硬件,只能通过调用本地方法即调用C++来完成线程相关的操作,我们继续探究刚才的问题:
unsafe.compareAndSwapInt(this, valueOffset, expect, update);这步是怎样完成的,我们通过源码知道这是本地方法的实现方式,那这些参数有何意义,首先this当前自身对象,valueOffset表示该对象在内存中的地址值,expect代表预期原值,如果当前对象在内存中的值和期望原值相等,就将内存的值改为新值也就是update

2.getAndIncrement()

这个方法和i++的目的一样,使当前的值自增1,具体用法如下:

AtomicInteger atomicInteger = new AtomicInteger(2020);
atomicInteger.getAndIncrement()

AtomicInteger类的值自增1,我们看一下源代码:


public class AtomicInteger extends Number implements java.io.Serializable {
	private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    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类中的getAndAddInt()方法,而参数valueOffset是获得当前对象的内存偏移值,我们进入这个方法:

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;
    }

我们可以清楚的发现和我们之前介绍的同样用到了compareAndSwapInt()方法,这里不赘述,需要注意的是var5 = this.getIntVolatile(var1, var2);表示从内存地址为var2处获取对象var1的值,如果和期望原值var5相同,就替换为var5+var4var4就是传下来的参数1,这里通过do... while的方式,是通过一种自旋锁的方式,有兴趣的同学可以去查一下

总结:

java.util.concurrent.atomic包提供了许多可以进行原子更新的类,这些类都使用了unsafe来实现原子操作也就是Compare And Swap(CAS)