CAS
cas全称是 Compare and swap 也就是比较-置换
是轻量级锁
- 在我们程序执行的基本步骤是 取值—计算—赋值 三步,如何保证这三步的原子性是并发编程的基础。我们要实现并发程序的原子性就要保证在取值计算赋值操作时,不能有其他线程在同时修改,原子性的基本实现分为三步,例如AtomicInteger的递增计算
- 第一步:取出旧值
- 第二步:计算新值
- 第三步:比较旧值与内存中的值是否相同,相同赋值,不同则自旋继续1.2.3的步骤
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;
}
底层实现
CAS的方法基本集中在Unsafe中,内部的方法基本是使用native修饰。调用的方法是底层C++代码,在判断CPU核数>1后,返回lock指令,使用汇编语言的cmpxchgq调用不通的操作系统语言实现比较交换的原子性。lock cmpxchgq实现交换的原子性,会对内存加缓存行锁,如果内容超过64字节会添加总线锁。
CAS是不走系统调用的,是在用户态的代码中“插入” cmpxchg 汇编指令,由这种CPU原语性质的汇编指令保证原子性。所以整体来看一直是在用户态代码中执行,而没有走入内核的代码。没有用户态/内核态之间的上下文切换。所以CAS的锁实现是轻量级的。
CAS实现的锁一定比重量级锁更加优异吗
不一定的
- 大部分场景下CAS锁比切换到操作系统级别的锁更优(例如计算非常快的情况下,只有少数抢占锁冲突出现。
- 如果在持有锁后需要进行长时间,大数据量的计算就适合重量级锁,让其他排队的线程进入队列挂起,释放持有的CPU资源,方便有需要的线程使用,避免计算资源的浪费。
ABA问题
- CAS是将旧值与新值比较的后相同再置换,但是存在一种情况,在置换的时候有其他线程修改了这个值,然后又恢复到原值,这时候原线程是不知道的,比较依然是没问题的,这就会出现ABA问题。在一般情况下不在乎值变迁情况的可以忽略,如果需要完全执行过程不有其他线程参与可以使用AtomicStraminteger,会通过版本记录的方式,对每次变更执行version+1操作。同时判断值相等和版本相同才精选置换。