深入理解CAS
Posted 杀手不太冷!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解CAS相关的知识,希望对你有一定的参考价值。
深入理解CAS
CAS的定义
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。
CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。
这样说或许有些抽象,我们来看一个例子:
CAS的例子
1.在内存地址V当中,存储着值为10的变量。
2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。
3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。
4.线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。
5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。
6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。
7.线程1进行SWAP,把地址V的值替换为B,也就是12。
从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。
4.CAS存在的问题
1.CPU开销较大
在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
2.不能保证代码块的原子性
CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。
3.ABA问题
这是CAS机制最大的问题所在。
使用CAS实现一个线程安全的计数器
输出结果,如下图:
CAS计数器的全部代码如下:
package org.xzy;
/*
* 使用CAS实现一个线程安全的计数器
* */
public class CASTest {
public static void main(String[] args) {
CASCounter casCounter=new CASCounter();
for(int i=0;i<1000;i++){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(casCounter.incrementAndGet());
}
}).start();
}
}
}
class CASCounter{
volatile private long value=0;
public long getValue(){
return value;
}
private boolean compareAndSwap(long expectedValue,long newValue){
//如果当前value的值与期望的expectedValue的值一样,就把当前的Value字段替换为newValue值
if(value==expectedValue){
value=newValue;
return true;
}else{
return false;
}
}
//定义自增的方法
public long incrementAndGet(){
long expectedValue;
long newValue;
do{
expectedValue=value;
newValue=expectedValue+1;
}while(!compareAndSwap(expectedValue,newValue));
return value;
}
}
CAS中的ABA问题
CAS中的ABA问题其实也就是,如果共享变量的当前值和当前线程提供的期望值相同,共享变量可能已经被其它的多个线程修改过,只不过多个线程修改的最后结果是把这个共享变量又修改为了最初值。
CAS实现原子操作背后有一个假设:共享变量的当前值与当前线程提供的期望值相同,就认为这个变量没有被其它线程修改过
实际上这种假设不一定总是成立,比如说现在有共享变量count=0
A线程对count值修改为10
B线程对count值修改为20
C线程对count值修改为0
当前线程看到count变量的值现在是0,现在是否认为count变量的值没有被其它线程更新呢?这种结果是否能够接受?
这就是CAS中的ABA问题,即共享变量经历了A->B->A的更新
是否能够接受ABA问题跟实现的算法有关
如果想要规避ABA问题,可以为共享变量引入一个修订号(时间戳),每次修改共享变量时,相应的修订号就会增加1
ABA变量的更新过程变成:[A,0]->[B,1]->[A,2],每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确变量是否被其它线程修改过。AtomicStampedReference类就是基于这种思想。
以上是关于深入理解CAS的主要内容,如果未能解决你的问题,请参考以下文章