CAS-原子操作解析
在多线程条件下(A,B两个线程),假如i = 2,我们想通过i++的方式让 i 的值变为4,可能执行不成功,因为i++不是原子性操作,在底层不是表面上的i++一步完成,而是分成几步来操作,所以两个线程很可能拿到的值都是2,最终结果可能为3,所以在进行这种操作的时候,一般是 加锁来实现,但加锁会使效率低下,因此,从jdk5
开始,java提供了java.util.concurrent.atomic
包来进行原子性操作
而这里面的原子类的方法都差不多,并且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+var4
,var4
就是传下来的参数1,这里通过do... while
的方式,是通过一种自旋锁的方式,有兴趣的同学可以去查一下
总结:
java.util.concurrent.atomic
包提供了许多可以进行原子更新的类,这些类都使用了unsafe
来实现原子操作也就是Compare And Swap(CAS)
上一篇: 5、CAS原子操作
下一篇: 分布式ID生成解决方案