CAS操作

1.CAS的概念与实现

        CAS的全称是 compare and swap

        涉及到的操作是寄存器A中的值与内存的值M进行比较,如果值相同就把寄存器B的值和M进行交换

用一段伪代码来理解一下

        address 是内存地址 代表 M

        expectValue 代表寄存器 A

        swapValue 代表寄存器 B

        他先判断一下内存地址的值 M 和接收到的值确认一下是不是一样的,如果一样就将值进行交换并且返回一个 true

        CAS 操作是一条 CPU 指令,比不是上述一段代码,相当于 CAS 操作是原子的

2.CAS 的应用

        CAS 相当于我们不需要进行加锁也能实现很多操作,下面举两个例子

(1) 原子类

        Java标准库提供了 Atomlnteger 类,可以保证 ++ 和 -- 的操作线程安全

        举个例子先

public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(0);
        System.out.println(atomicInteger.getAndIncrement());//打印0
        int val = atomicInteger.get();
        System.out.println(val);//打印1
    }

        还是使用一段伪代码来解释一下,比较好描述这一段

        这个操作相当于 i++;

         其中的 value 相当于是内存中最开始存储的数字,也就是我们代码中的初始值 0

        oldvalue 相当于一个寄存器( Java 中没法表示寄存器的值,只能用变量表示),先存储一下内存的值也就是 0

        (1) while 这个是利用 CAS 操作来判断 value 和 oldvalue 是否相同,如果相同的话就返 true ,此时循环不会执行,并且 value++ ,最终返回原来 oldValue 的值也就是0

最终就是value被修改成 1 ,返回oldValue也就是 0

        (2) 如果CAS发现 value 和 oldValue不相等的话返回 false ,循环开始执行,更新一下 oldValue 的值,如果此时发现相等就会重复 (1) 的操作

此处的CAS就是在确认 value 的值是不是有人动过,如果动过,就更新一下,没动过就在内存值 ++ 一下

(2) 自旋锁

        先放一段伪代码来分析自旋锁,在 synchronized 里由偏向锁向轻量级锁转变后,就会产生自旋锁

public class SpinLock {
    private Thread owner = null;  // 表示当前持有锁的线程,初始为 null 表示没有线程持有锁。

    public void lock() {
        // 通过 CAS 看当前锁是否被某个线程持有。
        // 如果这个锁已经被别的线程持有,那么就自旋等待。
        // 如果这个锁没有被别的线程持有,那么就把 owner 设置为当前尝试加锁的线程。
        while (!CAS(this.owner, null, Thread.currentThread())) {
            // 自旋等待,直到 CAS 成功
        }
    }

    public void unlock() {
        this.owner = null;  // 释放锁,将 owner 置为 null。
    }
}

        owner 表示当前锁是否有被持有

        CAS 操作时表示如果当前的 owner 为 null ,比较就成功,将当前线程的引用地址放到 owner 上,完成几所

        如果比较不成功,说明 owner 被人持有, CAS 就会返回 false , while 循环就会一直进行,知道 owner 被unlock 设为 null 的时候,CAS操作返回 true 时,进行加锁

        好处 : 一旦锁释放立刻就能拿到

        坏处 : cpu 会一直处在忙等状态,浪费cpu的资源

3. ABA 问题

        由于 CAS 是进行对值的比较,有时候 value 和 oldValue 对比是相同的,但是不代表没变过

而是从a -> b -> a,此时有一定概率会出现一些 bug

例子

        比如现在有一个共享变量 value 的值是 A

        有三个线程 : t1,t2,t3

        时间点1. 线程 t1 读取 value 的值并准备执行 CAS 操作

        当 t1 读取 value 的值,存储到 oldValue 时(此时这个操作并不是原子的), t1 的目的是将 A 修改成 C 

        时间点2. 线程 t2 更新 value 的值

        线程 t2 读取了 value 的值为 A , t2 将 value 的值修改成了 B

        时间点3.线程 t2 再次更新 value 的值

        线程 t2 再次操作将 value 的值从 B 更改成 A

        时间点4. 线程 t1 继续执行 CAS 操作

        线程 t1 检查 value 值发现与最开始读到的值相同,执行 CAS ,认为没有被修改过,将 value 的值更新成 C

举个生动点的例子

        比如我要去 ATM 机上取钱,取50快,我原来有100快

        设置了两个线程,两个线程都是通过CAS操作来执行,都是-50

        正常情况下,两个线程都获取到 100 块钱并存入 OldValue

        •  其中一个线程开始执行 CAS 操作,另一个线程则处于等待状态。

        •  先进行的线程发现 OldValue 和当前的 value 相等,于是扣除 50 块钱,操作完成并退出。

        •  第二个线程发现 OldValuevalue 不一样,所以没有执行扣款操作,并返回 false


        异常情况下,两个线程同样获取到 100 块钱并存入 OldValue

        •  其中一个线程开始执行 CAS 操作,另一个线程继续等待。

        •  先执行的线程发现 OldValuevalue 相等,扣除 50 块钱。但此时你转了 50 块钱到我的卡 上,使得 value 又变成了 100

        •  先执行的线程退出后,第二个线程发现 OldValue 和当前的 value 依然相等,于是错误地再次扣除 50 块钱,最终出现了多次扣款的 bug。

        CAS只能对比最终值是否相同,无法确定值中间是否发生变化

解决方法

        我们可以引入版本号变量,约定版本号只能增加

        每次CAS对比的时候就不是数值本身,而是对比版本号

        如果发现当前版本号和之前读到的版本号一致, 就真正执行修改操作, 并让版本号自增; 如果发现当前版本号比之前读到的版本号大, 就认为操作失败

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值