一.什么是CAS
CAS(compare and swap) 比较并且交换. CAS是一个cpu指令,是原子的不可再分.因此基于CAS就可以给我们编写多线程的代码提供了新的思路---->使用CAS就不用使用加锁,就不会牵扯到阻塞,也称为无锁化编程
下面是一个CAS的伪代码:
address是一个内存地址,expectValue和swapValue都表示一个寄存器中的值,比较比较的是内存地址中的值和expectValue的值是否一样,交换就是:如果一样就把address和swapValue的值进行交换.如果不相同,则一直循环对比,直到成功为止.
通俗来讲就是:比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止.
举一个详细的例子:(count++)
现在有两个线程t1和t2并发执行count++这个操作,count++需要牵扯到三个操作,①读取:从内存中读取当前计数器的值.②.读取到的值+1.③.写入:将+1的值写回到内存的计数器中.
- 开始执行,address中的值为0,t1线程执行完①操作,此时读取到的值是0(expectValue的值为0),address中的值也为0.此时能够进行++操作.
- 但是系统调度t2线程执行执行①②③.也可以执行count++ 的操作.执行完后内存地址中的值为1.
- 然后线程调度回t1,此时发现address(1)和expect Value(0)的值不一样,此时就不能进行++操作.
- 重新读取内存中当前计数器的值,读取过后expectValue的值变为了1,此时线程1就能进行++操作了.
此时就能保证线程安全了.
下面我将在Java中用CAS来解决count++这个问题
代码实例:
import java.util.concurrent.atomic.AtomicInteger;
//使用原子类解决问题
public class Test11 {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i = 0;i<5000;i++){
count.getAndIncrement();
//++count
//count.incrementAndGet();
}
});
Thread t2 = new Thread(()->{
for(int i = 0;i<5000;i++){
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count = " +count.get());
}
}
运行结果:
CAS的优缺点
- 优点:
- 避免竞态条件:CAS操作通过比较内存位置的当前值与预期值,只有在它们相等时才更新该内存位置的值,从而避免了多线程下的竞态条件。
- 高并发性能:由于CAS操作不会引起线程阻塞和上下文切换的开销,因此它具有很高的并发性能,适用于高并发场景。
- 适应性强:CAS操作可以应用于各种高并发场景中,是Java并发包(java.util.concurrent)的基础。
- 缺点:
- ABA问题:在执行CAS操作时,如果共享变量的值从A变为B又变回了A,CAS操作无法区分这种情况,因为它只比较值而不是版本或标识符。
- 硬件限制:CAS操作的性能可能受到硬件指令集的限制,不同的硬件平台可能对CAS的支持程度不同。
- 高CPU消耗:在高并发环境下,CAS操作可能会因为多次尝试而消耗较高的CPU资源。
- 不支持多步操作:CAS操作只能保证单个共享变量的原子性更新,对于需要多步操作的复合动作,需要通过循环CAS或其他机制来实现。
CAS的ABA问题.
什么是ABA问题:
如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了.
举个例子:假设去银行取钱,初始情况下账余额为1000 , 现在要取500!!取钱的时候ATM卡了,我按了一下卡了,就又摁了一下,此时就产生了两个线程来进行扣款,假设是由CAS模式来进行扣款=====>此时如果出现第三个线程在t1线程执行完CAS的第一步,t2线程执行完扣款操作了,此时第二次扣款操作也会执行:此时就会产生bug.相当于两次取款都变得有效了.
对于ABA问题的解决方法:
①:约定数据变化是单向的(只能增加或只能减少),不能是双向的(又能增加又能减少)
②.对于本身就需要双向变化的数据,可以给它引入一个版本号,版本号的数字是只能增加不能减少的