CAS,compare and swap的缩写,中文翻译成比较并交换。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
在并发中,我们需要对一个数据进行更改,如果使用锁来保证原子性,首先在性能方面会设计到底层操作系统内核线程切换,这个开销是很大的,如果使用 CAS 实现的话,相比较之下不会设计到内核切换,开销比较轻。
使用伪代码表示
if (value == expectedValue) {
value = newValue;
}
一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体 ——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。
CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操 作就通过CAS自旋实现的。 CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。
在Java中使用 CAS
CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操 作
可以看到是本地方法,是由 JVM 实现的。 接收四个参数分别是
对象的实例,内存偏移量,字段期望值,字段新值。
CAS 虽然解决了原子性,但是有一些问题
当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马 上将其修改为A,此时其他线程不感知,还是会修改成功。
main 线程不清楚另一个线程对这个变量进行了修改,可能误认为没有更改过。
ABA问题的解决
可以在每一次修改时给一个版本号,这也就是乐观锁的由来。
比如 Java中AtomicStampedReference
类就提供了这样的功能
三个线程 t1,t2,t3 分别对变量进行修改,最后t3在更改为初始值。
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
boolean b = atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
while (b) {
if (b) {
System.out.println(" t1 修改成功 ");
return;
} else {
System.out.println(" t1 修改失败 ");
}
}
}, "t1 ").start();
new Thread(() -> {
boolean b = atomicStampedReference.compareAndSet(2, 3, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
while (b) {
if (b) {
System.out.println(" t2 修改成功 ");
return;
} else {
System.out.println(" t2 修改失败 ");
}
}
}, "t2 ").start();
new Thread(() -> {
boolean b = atomicStampedReference.compareAndSet(3, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
while (b) {
if (b) {
System.out.println(" t3 修改成功 ");
return;
} else {
System.out.println(" t3 修改失败 ");
}
}
}, "t