CAS的全称是 Compare And Swap(比较并交换),它是并发编程中的一个重要概念。本文结合Java的多线程操作来讲解CAS算法。
CAS算法的优势是可以在不加锁的情况下保证线程安全,从而减少线程之间的竞争和开销。
目录
一、CAS算法的内容
1、基本思想和步骤
CAS算法的基本思想是,先比较内存M中的值与寄存器A中的值(旧的预期值,expectValue)是否相等,如果相等,则将寄存器B中的值(新值,swapValue)写入内存;如果不相等,则不做任何操作。整个过程是原子的,不会被其他并发操作中断。
这里虽然涉及到内存与寄存器值的“交换”,但更多时候我们并不关心寄存器中存的值是什么,而是更关心内存中的数值(内存中存的值即变量的值)。所以这里的“交换”可以不用看成交换,直接看成是赋值操作就行,即把寄存器B中的值直接赋值到内存M中了。
CAS 操作包括三个操作数:一个内存位置(通常是共享变量)、期望的值和新值。CAS 操作的执行过程如下:
- 读取内存位置的当前值。
- 比较当前值与期望的值是否相等。
- 如果相等,将新值写入内存位置;否则,操作失败。
2、CAS伪代码(如果把CAS想象成一个函数)
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
上面给出的代码只是伪代码,并不是真实的CAS代码。事实上,CAS操作是一条由CPU硬件支持的、原子的硬件指令,这一条指令就能完成上述这一段代码的功能。
“一条指令”和“一段代码”的最大区别在于原子性。上述这一段伪代码不是原子的,运行过程中可能随着线程的调度有概率产生线程安全问题;但原子的指令不会有线程安全问题。
同时,CAS也不会有内存可见性的问题,内存可见性相当于是编译器把一系列指令的进行了调整,把读内存的指令调整成读寄存器的指令。但CAS本身就是指令级别读取内存的操作,所以不会有内存可见性带来的线程不安全问题。
因此,CAS可以做到不加锁也能一定程度地保证线程安全。这样就引申出基于CAS算法的一系列操作。
二、CAS算法的应用
CAS可以用于实现无锁编程。实现原子类和实现自旋锁就是无锁编程的其中两个方式。
1、实现原子类
标准库 java.util.concurrent.atomic 包中有很多类使用了很高效的机器级指令(而不是使用锁) 来保证其他操作的原子性。
例如, Atomiclnteger 类提供了方法 incrementAndGet、getAndIncrement 和 decrementAndGet、getAndDecrement,它们分别以原子方式将一个整数自增或自减。
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(()->{
//num++
num.getAndIncrement();
//++num
num.incrementAndGet();
//num--
num.getAndDecrement();
//--num
num.decrementAndGet();
});
例如,可以安全地生成数值序列,如下所示:
import java.util.concurrent.atomic.Atomic