CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
- 比较 A 与 V 是否相等。(比较)
- 如果比较相等,将 B 写入 V。(交换)
- 返回操作是否成功。
当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。可见 CAS 其实是一个乐观锁。
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:
简而言之,是因为硬件予以了支持,软件层面才能做到。
可以用于实现自旋锁,演示代码:
public class SpinLock {
private AtomicReference<Thread> sign =new AtomicReference<>();
public void lock(){
Thread current = Thread.currentThread();
// 不放弃 CPU,一直在这里旋转判断
while(!sign .compareAndSet(null, current)){
}
}
public void unlock (){
Thread current = Thread.currentThread();
sign.compareAndSet(current, null);
}
}
用于实现原子类,示例代码:
public class AtomicInteger {
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
}
public class 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;
}
}
无锁编程:不使用锁,直接使用CAS来保证线程安全。
下边我详细介绍一下某个变量利用CAS进行 ++ 操作,
来看看下边伪代码:
int size = 0;// 成员变量
// 多线程调用 add
void add() {
int oldSize = size;
while(!CAS(&size,oldSize,oldSize +1)) {
oldSize = size;
}
}
int oldSize = size;
while(!CAS(&size,oldSize,oldSize +1)) {
oldSize = size;
}
认为size 和 oldsize 相等就更新 size 值(说明没有被其他线程修改),并且返回true,循环结束。
对于线程2:
int oldSize = size;
while(!CAS(&size,oldSize,oldSize +1)) {
oldSize = size;
}
此时判断size 的值和oldSize不相等,不更新 size 值,并返回flase循环就要继续
int oldSize = size;
while(!CAS(&size,oldSize,oldSize +1)) {
oldSize = size;
}
此时 oldsize和size 相等此时修改size, 并且返回true,循环结束。
这个代码的本意就是,根据 oldSize 的值来判断读取 size 和修改size 之间是否被其他线程 改过了,但是也存在另外一种情况就是,在两个线程之间可能出现其他线程针对size 进行修改,,但是也有可能改了之后又被其他线程把size 给改回去。这也就是ABA问题。
标准库中也基于CAS封装了一些原子类:
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
CAS的缺陷就是区分不了这两种情况:
该线程看到size = 0;
1、size就没有修改过就是原来的值;
2、size 被别的线程改成了1,又被另一个线程修改为0.
这就是ABA问题。
解决ABA问题思路:
解决ABA最简单的方案就是给值加一个修改版本号,每次值变化,都会修改它版本号,CAS操作时都对比此版本号。