CAS问题

简介: CAS问题

🔎什么是CAS

CAS

全称Compare and swap(比较并交换)

解释

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


🔎伪代码解析

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false;
}

解释

if ( &address == expectedValue)
如果寄存器A的值和内存B的值相等

&address = swapValue
将寄存器C的值与内存B的值交换

注意

上述代码只是伪代码,用来理解CAS的执行过程

针对上述代码,多线程情况下可能会引起安全问题

而CAS是原子性的,故不会引起安全问题


🔎CAS是如何实现原子性

基于硬件(CPU)实现的原子性


🔎CAS的应用

🌻实现原子类

基于 java.util.concurrent.atomic 包中的类

一个典型的代表就是 AtomicInteger 类

举个栗子🥝

public static void main(String[] args) throws InterruptedException {
        AtomicInteger num = new AtomicInteger(0);
        int n = 50_000;
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < n; i++) {
              //num++
                num.getAndIncrement();
            }
        }, "t1线程");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < n; i++) {
              //num++
                num.getAndIncrement();
            }
        }, "t2线程");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(num);
}

针对上述代码

如果是非原子的自增操作(num++),就可能存在线程安全问题

但是由于CAS本身就是原子的,所以不会存在线程安全问题

也就是说,CAS操作可以不用加锁就能够解决线程安全问题


🌻实现自旋锁

伪代码解析🥝

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

解释

Thread owner = null 代表当前的锁没有被线程持有

while(!CAS(this.owner, null, Thread.currentThread()))

Thread.currentThread() 代表当前线程

null 代表空

this.owner 代表当前锁

如果当前 owner 是null,就把当前线程的引用设置到 owner(加锁),循环结束

如果当前 owner 不是null,代表已经有其他线程加锁成功

此时就一直进行循环比较,不停的尝试加锁(速度很快)

public void unlock () 代表解锁

this.owner = null 解锁就是将owner设为null


🔎ABA问题

什么是ABA问题

通俗点来说就是从A变成了B最后又变成了A

A–>B–>A

CAS是对比内存和寄存器中的值,查看是否相同

(通过对比,判断内存中的值是否改变过)

但是对比时发现内存中的值是相同的,也不能确定内存中的值就没发生过改变

解释

t1线程读取内存中的值时,t2线程将内存中的值从A变成了B最后又变成了A

这时t1线程读取到的内存的值仍然是A,但是内存的值却发生了变化

这就是ABA问题

举个栗子🥝

我们买手机的时候

可能买到的是原装手机(没有更改的)

也可能买到的是翻新机—别人用了很久的,然后手机厂商进行一系列操作(换个壳,去去尘等),这样的手机看起来虽然和原装的没太大区别,但终究也只是看起来

原装手机就类似与内存中的值没有发生过改变

翻新机就类似于A–>B–>A


🌻ABA问题可能引起的BUG

举个栗子🥝

滑稽老哥去ATM机取款50元,他的银行账户有100元,取款之后变为50元

ATM并发执行两个线程

(1)线程1获取到当前存款为100元,执行扣款50元 线程2获取到当前存款为100元,执行扣款50元

(2)线程1执行扣款成功,滑稽老哥存款变为50 线程2阻塞等待

(3)此时正好滑稽老哥的朋友向滑稽老哥转账50元请滑稽老哥帮买东西

(4)滑稽老哥的存款又变为了100元

(5)线程2执行,发现存款仍为100元,执行扣款50元,此时滑稽老哥的账户又被扣款了50元


🌻ABA问题的解决方案

引入版本号,规定版本号只能增加

每次CAS对比的时候,不仅需要对比数值,而且需要对比版本号

如果进行修改数值,就将版本号+1

这样再去对比ABA问题时

发现版本号已经发生改变,就证明数值发生了变化

发现版本号没有发生改变,证明数值没有发生变化

举个栗子🥝

滑稽老哥去ATM机取款50元,他的银行账户有100元,取款之后变为50元

ATM并发执行两个线程,此时引入版本号,版本号为0

(1)线程1获取到当前存款为100元,执行扣款50元 线程2获取到当前存款为100元,执行扣款50元

(2)线程1执行扣款成功,滑稽老哥存款变为50 线程2阻塞等待,版本号变为1

(3)此时正好滑稽老哥的朋友向滑稽老哥转账50元请滑稽老哥帮买东西,版本号变为2

(4)滑稽老哥的存款又变为了100元

(5)线程2执行,发现存款仍为100元,但版本号发生了改变,此时就会执行失败

(6)滑稽老哥成功保住了50元


🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍

大家有什么不太理解的,可以私信或者评论区留言,一起加油

相关文章
|
存储 资源调度 安全
H3C CAS系列 一、CAS初认识
对于虚拟化,可能第一时间大家想到的是虚拟机,而对于虚拟机大家可能第一时间想到的就是我们大多数人都可能比较熟悉的VMware系列产品,比如常用VMware Workstation Pro 、VMware esxi。 而今天我带大家一起认识一款我们国产的虚拟化软件 H3C CAS。
1769 0
|
6月前
|
算法 调度 数据安全/隐私保护
什么是CAS锁
什么是CAS锁
78 0
|
6月前
|
Java API
CAS的超~详细介绍
CAS的超~详细介绍
|
6月前
|
存储 算法 Java
|
6月前
|
算法
原子操作CAS
原子操作CAS
38 0
|
6月前
基于CAS实现自旋锁
基于CAS实现自旋锁
44 0
|
6月前
|
算法 Java 关系型数据库
CAS
本文主要讲解java中cas的概念及原理
63 0
|
安全
CAS和多线程密切相关的东西!
CAS和多线程密切相关的东西!
30 0
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
197 0
|
算法 安全 Java
简单理解CAS
简单理解CAS
276 0
简单理解CAS