什么是CAS
CAS是compare and swap的缩写,译为比较与交换,都说它厉害,那么它究竟厉害在哪呢?它可以做到原子性的修改内存中的数据,这是什么概念?我们的锁不就是为了处理数据修改带来的线程安全问题吗?这么厉害的玩意使用是个什么样的流程,让我们来研究一下:
这样为什么是原子性的呢,因为每次数据修改前都会先比较我们拷贝的A‘和A的值是否相等,如果期间有其他线程已经修改过A的值,那么就会重新获取A’的值,再继续判断,就不会出现两个线程操作同一数据,出现都操作的旧数据的问题了。
伪代码:(cas是硬件指令,伪代码只是帮助理解)
boolean cas(A,A',B){
if(A==A'){
A=B;//修改内存A的值为B
return true;
}
return false;
}
CAS的用处
1.原子类
通过借助cas的力量,java标准库提供了java.util.concurrent.atomic包,里面包含如下类:
这些类如何使用在这里不展开说,我们只说一个最典型的:
这个类的常用方法有:
incrementAndGet() 前置++
getAndIncrement() 后置++
getAndDecrement() 后置–
decrementAndGet() 前置–
这个类内部有一个成员变量count,通过这些方法可以原子性的修改count的值,多半适用于计数器。
2.实现自旋锁
我们都知道自旋锁是没抢到锁,不挂起等待而持续的去尝试获取锁,那么这个持续的尝试获取锁也是借助了cas的力量,通过cas持续的观察当前锁是被哪个线程占有,只要发现当前锁没有线程占用,里面尝试获取。伪代码如下:
Thread getLock=null;//该变量代表哪个线程获取当前锁
void lock(){
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.(getLock是否为null)
// 如果这个锁已经被别的线程持有, 那么就自旋等待.(getLock!=null)
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.(getlock==null)
while(!CAS(this.getLock, null, Thread.currentThread())){}
}
}
CAS的缺点(ABA问题)
说了cas那么多优点,它一点缺点都没有是不是有点不太礼貌了。正所谓人无完人,cas也是这样,那么这里就介绍一下cas的缺点也就是ABA问题。
什么是ABA问题:
因为我们cas的修改前提是判断A‘和A是否相同,这就有可能出现其实A已经被修改过,只是值没变,但A已经不是哪个A了,就像我们的滑稽老哥已经不只是谈过一次女票了。虽然大部分情况下ABA问题并没有什么影响,但再一些特殊情况还是有影响的,所以这个问题必须要解决。
解决方案:引入版本号(或者修改时间)
通过比较我们的版本号,来判断当前内存值有没有被修改过。具体流程:内存A不仅存我们的数据同时存储一个版本号V,每次修改成功时,版本号V+1,每次修改前再拷贝一份版本号并+1称作V’,即将修改的时候,比较V‘是否大于V,是就修改,不是就重新获取V’,这样就可能有效的解决ABA问题
相关面试题:
- 讲解下你自己理解的 CAS 机制
- ABA问题怎么解决?
结束语
又是一个小细节