什么是CAS
是一种思想,是一种实现线程安全的算法,同时也是一条CPU指令,比如Compare and Swap这一条指令就能完成“比较并交换”原子操作。
CAS有三个操作数:内存值V、预期值A、要修改的值B,当且晋档预期值A和内存值V相同时,才将内存值修改为B,否则什么都不做。最后返回现在的V值。
用大白话说就是:我任务V的值应该是A,如果是的话那我就把它改成B,如果不是A(说明被别人修改过了),那我就不修改了。
这种思想最终通过调用CPU的一些特殊指令来实现,这些指令由CPU保证了“比较和交换”两个动作的原子性。
分析在java中是如何利用CAS的CPU指令实现“比较和交换”原子操作
让我们来看看AtomicInteger是如何通过CAS实现并发下的累加操作的,以AtomicInteger的getAndAdd方法为突破口。
getAndAdd方法
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
可以看出,这里使用了Unsafe这个类,这里需要简要介绍一下Unsafe类:
Unsafe类
Unsafe是CAS的核心类。Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。不过尽管如此,JVM还是开了一个后门,JDK中有一个类Unsafe,它提供了硬件级别的原子操作。
AtomicInteger加载Unsafe工具,用来直接操作内存数据
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updates
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); }
}
private volatile int value;
public final int get() {
return value;
}
在AtomicInteger数据定义的部分,我们还获取了unsafe实例,并且定义了valueOffset。再看到static块,懂类加载过程的都知道,static块的加载发生于类加载的时候,是最先初始化的,这时候我们调用unsafe的objectFieldOffset从Atomic类文件中获取value的偏移量,那么valueOffset其实就是记录value值在内存中的偏移地址。
有了value在工作内存中的偏移地址之后,我们就可以用unsafe直接操作这个地址了,通过这个地址我们可以获取原值,也可以写入新值。
因为是修改工作内存中的value值,所以value是用volatile修饰的,保证了多线程之间不会出现可见性的问题。
接下来继续看Unsafe的getAndAddInt方法的实现
// AtomicInter类
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// Unsafe类
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;
}
我们看var5获取的是什么,通过调用unsafe的getIntVolatile(var1, var2),这是个native方法,其实就是获取var1中,var2偏移量处的值。var1就是AtomicInteger,var2就是我们前面提到的valueOffset,这样我们就从内存里获取到现在valueOffset处的值了。
现在重点来了,compareAndSwapInt(var1, var2, var5, var5 + var4)其实换成compareAndSwapInt(obj, offset, expect, update)比较清楚,意思就是如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作。
加法意味着只要在最新的值上加上我要加的值即可,我先取出工作内存中最新的值(有volatile保证最新),然后进行“比对并交换”原子操作。比对成功说明我刚从工作内存中取出的值是最新的,可以往该值加上我要加的值。比对不成功,说明我刚刚取出工作内存中的值的确是最新的,但不幸过后就被别的线程修改了,我比对的这一刻已经不是最新的了,所以我不能往该值加上我要加的值(加的话会导致加少),只能再次去工作内存中获取最新的值再加上,不释放CPU,也就是在自旋。
-->自旋+cas = atomicInteger
Unsafe类中的compareAndSwapInt本地方法
根据偏移量拿到value值在工作内存中的地址
通过Atomic:cmpxchg实现原子性的比较和替换,其中x是即将更新的值,e是原内存的值。
CAS思想在别的地方的体现
- 在数据库更新操作中,更新语句的where条件会带上一个版本号,若版本号不符合,则不进行更新操作。随后再select出最新的版本号,再尝试去进行更新操作。如此反复。
缺点
- ABA问题
可以通过版本号来解决 - 竞争激烈时,自旋时间可能会过长