【十二】一文带你迅速掌握 CAS

1. 什么是CAS

compare and swap,字面意思:比较和交换

寄存器 A 的值 和 内存 M 的值进行对比,如果值相同,就把寄存器B的值和M的值进行交换

// 伪代码
/*
address 内存地址
expectedValue 寄存器A
swapValue 寄存器B
*/
boolean CAS(address, expectValue , swapValue){
	if(&address == expectedValue){
		&address == swapValue;
		return true;
	}
	return false;
}

上述是伪代码来描述下CAS的操作,但是我们要知道,CAS操作,是一条CPU指令!(是靠的CPU支持)是原子性操作!

2. CAS的应用

2.1 实现原子类

Java标准库中提供了 java.util.concurrent.atomic包,里面的类都是基于这种方式实现的

import java.util.concurrent.atomic.AtomicInteger;

public class Demo27 {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger num = new AtomicInteger(0);

        Thread t1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                num.getAndIncrement();
            }
        });

        Thread t2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                num.getAndIncrement();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(num.get());
    }
}

在这里插入图片描述
可以看出结果是对的!


同样使用一段伪代码介绍CAS原理,了解下getIncrement内部实现

// 伪代码
class AtomicInteger{
	private int value;
	public int getAndIncrement(){
		int oldValue = value;
		while( CAS(value,oldValue, oldValue+1) != true){
			oldValue = value;
		}
		return oldValue;
	}
}

在这里插入图片描述

2.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;
	}
}

在这里插入图片描述

3. CAS中的 ABA 问题

CAS的关键是对比 寄存器和内存中的值,看是否相同~(通过这个对比,来检测,内存是不是改变过)
万一对比的时候是相同的,但是不是没变过,而是从 a -> b -> a,此时就会一定概率出现问题了

举个简单例子:
假设一个老哥有100块的存款,有一天他来从ATM机上取50块钱,假设ATM机有两个线程,并发的执行 - 50 的操作
我们期望的是线程A -50成功,线程B -50失败
如果使用CAS 的方式完成可能会出现问题

正常过程
1) 存款100,线程 A 获取到当前款值为100,期望更新为50;线程B也获取到款值为100,期望更新为50
2)线程A执行扣款成功后,存款变为50,线程B阻塞等待
3)轮到线程B执行,发现存款为50,和之前读到100不同,执行失败

异常的过程
1)存款100,线程 A 获取到当前款值为100,期望更新为50;线程B也获取到款值为100,期望更新为50
2)线程A扣款成功,存款变为50,线程B阻塞中
3)在线程B执行前,突然有个老铁给账户打了50远,此时存款变为100
4) 轮到B执行,此时发现当前存款和之前读的一样,就又执行-50
**这个时候,就出现问题,扣款两次,老哥成了大冤种**~


如何去解决这个ABA问题? ABA关键是值会反复横条~如果约定数据只能单方向变化,问题就迎刃而解了!(只能增加或者只能减少)

此时我们可以引入一个 版本号 变量,约定 版本号 只能增加
我们再一次举上述例子:

假设老哥存款100,要求ATM机上取50,ATM机有两个线程,并发的执行-50操作
我们期望的是一个 -50 成功,一个 -50失败
为了解决上述出现ABA 问题,给余额引入一个版本号,初始为1
1)存款100,线程A获取到 100,版本号为1;线程B也获取到100,版本号为1
2)线程A执行扣款成功后,款值变为50,版本号改为2;线程B阻塞等待中
3)线程B执行操作之前,有个好心人给老公转了50,余额变为100,此时版本号改成3
4)当线程B执行时,发现当前版本号和之前获取版本号不对应,则操作失败

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一个想打拳的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值