一.什么是CAS
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
这个就大概介绍了CAS的工作流程
二.CAS是如何实现的
针对不同的操作系统,JVM用到了不同的CAS实现原理
- java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
- unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
- Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。
三.CAS的应用
1.实现原子类
我们都知道,在多线程环境下,我们的++操作不是线程安全的。那么我们的CAS就自己将 ++ 操作是现成了原子类。
import java.util.concurrent.atomic.AtomicInteger;
public class CAS {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
int ret = atomicInteger.incrementAndGet();
System.out.println(ret);
}
}
此时在Java中,为我们提供了这样的 ++ 原子类操作。以上代码的运行结果为 1 。
那么我们深究一下这其中的实现原理
先来看下面的伪代码:
class AtomicInteger {
private int value;
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value, oldValue, oldValue+1) != true) {
oldValue = value;
}
return oldValue;
}
}
首先要知道我们的CAS操作时原子的。在多线程环境下,那么他是如何保证每次读到的值,都是我们预期的值呢?
我们看下面的图片:
🎈第一步
两个线程都读取 value 的值到 oldValue 中. (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)
🎈第二步
线程1进行CAS操作,先进行比较,发现 oldValue 和 Value 都是相同的,然后进行oldValue+1操作,最后再写入到内存中。此时主内存中Value的值为 1
🎈第三步
此时线程2进行操作。再进行 比较 oldValue 和 Value 是否相同的时候,发现不相同。此时重新进入while循环!在循环的时候, 会刷新oldValue的值-->1,此时oldValued 和 Value的值就相同了。正常进行+1赋值操作。
🎈第四步
2.实现自旋锁
先来看自旋锁的伪代码:
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就自旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
自旋锁其实时一种锁的策略,它的实现原理就是如果当前的线程是有锁的,那么就一直while循环尝试获取锁,如果没锁了,那么就直接可以获取到CPU了。
四.CAS的ABA问题
我们先来看个例子:
假如我要去银行里取钱,我的存款是100元,我期望的是取出50元。
那么取款机创建了两个线程,假如线程1执行了-50的操作,线程2再执行的时候会报错。
1.正常的过程
2.异常的过程
3.解决办法
总结:CAS是面试中常考的问题,我们需要深度学习,并且理解其中的操作机制。