本文会从java的源码对CAS算法进行分析,分析出CAS底层实现和并发包的原子类操作用CAS而不用synchronized,此外还会分析CAS的缺点和ABA问题的解决方案,希望能够对大家有所帮助
1.什么是CAS
CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制。CAS 操作包含三个操作数 -- 内存位置、预期数值和新值。CAS 的实现逻辑是将内存位置处的数值与预期数值想比较,若相等,则将内存位置处的值替换为新值。若不相等,则不做任何操作。(更深层次的理解需要依靠汇编进行解释)
2.通过一个多线程累加一个变量案例来引入CAS
/**
* 对一个变量进行累加
*/
public class CASDemo {
public void add(){
nummber++;
}
public void atomicAdd(){
atomicInteger.getAndIncrement();
}
public static void main(String[] args) {
ShareData shareData = new ShareData();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
shareData.add();
shareData.atomicAdd();
}
},String.valueOf(i)).start();
}
while (Thread.activeCount()>2){
Thread.yield(); //会使这个线程由正在运行的running状态转变为等待cpu时间片的runable状态。
}
System.out.println(shareData.nummber); //结果小于等于20000
System.out.println(shareData.atomicInteger.get()); //结果的每次都等于20000
}
}
class ShareData{
volatile int nummber =0;
public void add(){
nummber++;
}
AtomicInteger atomicInteger = new AtomicInteger(0);
public void atomicAdd(){
atomicInteger.getAndIncrement();
}
}
通过这个案例,我们能够发现,对于一个int型的number就行++操作是线程不安全的,想详细了解,请看我的volatile 这篇文章链接如下:https://blog.csdn.net/oldshaui/article/details/89342858 ,但是使用JUC下面的AtomicInteger这个类就不会有问题,下面我们来看下AtomicInteger类的底层实现
我们来主要分析下getAndAddInt这个方法(白话分析):首先会执行do里面的数据,根据当前对象和内存地址,取到主存里面的值,进入while循环,里面的compareAndSwapInt是一个Native方法,由C++编写的,利用JNI调用的,这个就是CAS算法的核心,大致可以理解为,根据当前对象和内存地址,拿到主存的值,和当前线程的存储的值进行比较,如果一样,就进行更新,返回true,取反为false,跳出while循环,否则为false,取反为true,一直在while循环等待
为什么atomicInteger底层用CAS,而不用synchronize
CAS算法没有加锁,也可以保证线程安全,可以保证并发,synchronize只能保证线程安全,不能保证并发,加锁就会让程序串行化
CAS的缺点:
- 循环时间太长,我们看CAS的底层源码可以发现,程序可能一直都在while循环里面循环,知道等到主存的值和工作内存中的值一样,才可以跳出循环
- 只能保证一个共享变量的原子操作
- 引发ABA问题
什么是ABA问题
白话解释:我们已经理解了CAS算法,比如有两个线程A,B,线程B执行的速度非常快,线程B把初始值100改为101,又把101改为100,如果此时线程A进行更新时,就会发现,他的本地缓存值和主存的值一样,他就会认为这个值没有被改变过,这个过程就是ABA问题,我们可以发现这过程中存在些猫腻
ABA的解决方案:利用类似数据库乐观锁的机制,把每次更新操作都对应一个版本号,线程A去更新的时候,不光要判断当前线程的缓存值和主存的值是否一样,还要判断他拿到的版本号是否一致,AtomicStampedReference可以用来解决这个问题