相关阅读
- JUC源码简析 AbstractQueuedSynchronizer
- JUC源码简析 Condition
- JUC源码简析 CountDownLatch
- JUC源码简析 CyclicBarrier
- JUC源码简析 ReentrantLock
- JUC源码简析 Semaphore
简介
Compare and Swap,比较再交换;
CAS是一种无锁算法,有3个操作数:
- 内存值V
- 旧的预期值A
- 要修改的新值B;
当前仅当预期值A
和内存值V
相同时,将内存值V
修改为新值B
,否则什么都不做。
do {
从主内存备份旧数据;
基于旧数据构造新数据;
} while (!CAS(内存地址, 备份的旧数据, 新数据));
开销
CAS是CPU指令级的操作,是一个原子操作,效率很高;
CAS避免了请求操作系统来裁定锁的问题,直接在CPU内部搞定;但有cache miss的情况。
举例:CPU0对一个变量执行CAS操作,但该变量是在CPU7的高速缓存中,则会进行如下简化的事件序列:
- CPU0检查本地高速缓存,没有缓存行;
- 请求被转发到CPU0和CPU1的互联模块,检查CPU1的本地高速缓存,没有找到缓存行;
- 请求被转发到系统互联模块,检查其他三个管芯,得知缓存行被CPU6和CPU7所在的管芯持有;
- 请求被转发到CPU6和CPU7的互联模块,检查这两个CPU的高速缓存,在CPU7的高速缓存中找到缓存行;
- CPU7将缓存行发送给所属的互联模块,并且刷新自己高速缓存中的缓存行;
- CPU6和CPU7的互联模块将缓存行发送给系统互联模块;
- 系统互联模块将缓存行发送给CPU0和CPU1的互联模块;
- CPU0和CPU1的互联模块见缓存行发送给CPU0的高速缓存;
- CPU0现在可以对高速缓存中的变量执行CAS操作;
JDK中应用——Unsafe
sun.misc.Unsafe
中有很多使用CAS的方法,比如getAndAddInt
,代码如下:
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
compareAndSwapInt
底层代码如下:
int mp = os::is_MP(); //当前系统是否为多处理器
...
LOCK_IF_MP(mp) //cmpxchg指令是否增加lock前缀
cmpxchg ...
intel手册对lock
前缀的说明:
- 确保对内存的读-改-写操作原子执行;
- 禁止该指令与之前的和之后的读和写指令重排序;
- 把写缓冲区中的所有数据刷新到内存中;
第一点保证了CAS操作是一个原子操作,第二点和第三点所具有的内存屏障效果,保证CAS同时具有volatile
读和volatile
写的内存语义;
常见问题
-
只能保证一个变量的原子操作,多个变量如何保证?
解决:将多个变量封装成对象,然后使用AtomicReference; -
如何解决ABA问题?
解决:使用AtomicStampedReference;