什么是CAS/CAS的应用/CAS的ABA问题

文章目录

  • CAS
    • 1. 什么是CAS
    • 2. CAS的应用
      • 2.1 实现原子类
      • 2.2 实现自旋锁
    • 3. CAS的ABA问题
      • 3.1 什么是ABA问题
      • 3.2 ABA问题引来的BUG
      • 3.3 解决方案

CAS

1. 什么是CAS

CAS: 全称Compare and swap, 字面意思:”比较并交换“. 操作:

设V为内存中的值, A为寄存器中的值(旧的预期值), B也为寄存器中的值(需要修改的新值)

  1. 比较V和A是否相等(比较)
  2. 如果相等, 则将B赋给V, 返回true(交换)
  3. 如果不相等, 返回false

CAS伪代码

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

CAS是一个原子的CPU指令完成的, 这个指令不可拆分, 上述伪代码不是原子的.

它既然是原子的, 那么我们编写线程安全的代码时, 就可以借助CAS

所以, 基于CAS又能衍生出一套"无锁编程"

但是CAS的适用范围具有一定的局限性, 只能针对一些特定场景进行优化.

当多个线程同时对某个资源进行CAS操作, 只能有一个线程操作成功, 但是并不会阻塞其他线程, 其他线程只会收到操作失败的信号.

2. CAS的应用

2.1 实现原子类

我们在前文讲解线程安全问题时, 曾举过一个例子, 就是多个线程针对一个count变量++, 如果不进行加锁, 那么就会引发线程安全问题. 当时我们是使用的synchronized解决的问题.

其实我们还可以使用原子类, 可以更高效更简单的操作.

标准库中提供了java.util.concurrent.atomic包, 里面的类都是基于CAS来实现的原子类.

在这里插入图片描述

AtomicInteger, AtomicLong提供了自增/自减/自增任意值/自减任意值. 这些操作就是基于CAS按照无锁编程的方式实现.

//例子
public class Demo26 {
    private static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                //count++
                count.getAndIncrement();
//                //++count
//                count.incrementAndGet();
//                //count--
//                count.getAndDecrement();
//                //--count
//                count.decrementAndGet();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();

            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(count.get());
    }
}

伪代码实现:

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        //这里的比较, 为的是检查当前value是否被其他线程改变了.
        //如果相等, 证明没有被改变, 操作还是原子的
        //如果不相等, 证明被其他线程改变了, 此时要立即重新读取内存的值, 然后再次比较相等...
        while ( CAS(value, oldValue, oldValue+1) != true) {
            //如果value!=oldvalue, 那么就更新oldvalue, 继续CAS
            oldValue = value;
        }
        return oldValue;
    }
}

当两个线程并发的进行++操作时:

  • 如果不加任何限制, 会引发线程安全问题
  • 加锁保证线程安全: 通过锁, 强制两个++操作串行执行
  • 原子类/CAS保证线程安全: 借助CAS来识别当前是否出现了并发/穿插的情况.
    • 如果没穿插, 直接修改, 是安全的
    • 如果穿插了, 就重新读取内存中最新的值, 再次尝试修改.

2.2 实现自旋锁

自旋锁伪代码

public class SpinLock {
    //使用owner表示当前是哪个线程持有这把锁
    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问题

3.1 什么是ABA问题

ABA 的问题:

假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A.

接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要

  • 先读取 num 的值, 记录到 oldNum 变量中.

  • 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.

但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A

线程 t1 的 CAS 是期望 num 不变就修改. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这个时候 t1 究竟是否要更新 num 的值为 Z 呢?

到这一步, t1 线程无法区分当前这个变量始终是 A, 还是经历了一个变化过程.

在这里插入图片描述

3.2 ABA问题引来的BUG

账户里面有100元, 要取款50

假设出现了极端情况

摁第一次取款的时候, 卡了一下, 然后又摁了一次

这样便产生了两个"取款请求", ATM使用两个线程来处理这两个请求

假设按照CAS的方式进行取款, 每个线程这样操作:

  1. 读取账户余额, 放到变量M中
  2. 使用CAS判定当前实际余额是否是M, 如果是就把实际余额改成M-50, 如果不是, 放弃操作

正常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.

  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.

  3. 轮到线程2 执行了, 发现当前存款为 50, 和之前读到的 100 不相同, 执行失败.

异常的过程

  1. 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50.

  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.

  3. 在线程2 执行之前, 朋友正好往转账 50, 账户余额变成 100

  4. 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行扣款操作

这个时候, 扣款操作被执行了两次

t1扣款50, t3又增加50, 就让t2错误的认为没有线程修改过余额, 就又扣款了50.

这就是A-B-A问题产生的后果

3.3 解决方案

我们可以给要修改的值, 引入版本号, 让它只增长不减小.在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.

  • CAS 操作在读取旧值的同时, 也要读取版本号.

  • 真正修改的时候,

    • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1.

    • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了).

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/190575.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

火柴棒等式

枚举 只要在保证等式正确的基础上判断火柴棒有没有用完就可以 因为数比较小&#xff0c;而且我不知道最大的等式中的数是多少&#xff0c;索性就设置为999了 还好对效率要求不大&#xff08;doge&#xff09; 要不然就得自己慢慢改最大数来试了 代码如下&#xff1a; #in…

Android 单元测试初体验

Android 单元测试初体验 前言一、单元测试是什么&#xff1f;二、简单使用1.依赖2.单元测试代码简单模版及解释 总结 前言 当初在学校学安卓的时候&#xff0c;老师敢教学进度&#xff0c;翻到单元测试这一章节的时候提了两句&#xff0c;没有把单元测试当重点讲&#xff0c;只…

jQuery_09 事件的绑定与使用(on)

jQuery使用on绑定事件 jQuery可以给dom对象添加事件 在程序执行期间动态的处理事件 1. $("选择器").事件名称(事件处理函数) $("选择器") &#xff1a; 选择0或者多个dom对象 给他们添加事件 事件名称&#xff1a;就是js中事件名称去掉on的部分 比如单击…

PTA-7-53 身份证排序

题目&#xff1a; 输入n&#xff0c;然后连续输入n个身份证号。 将每个身份证的年月日抽取出来&#xff0c;按年-月-日格式组装&#xff0c;然后对组装后的年-月-日升序输出。 根据题目要求&#xff0c;代码实现如下&#xff1a; import java.util.Scanner; import java.uti…

linux之间的免密通信原来是这么的简单

linux之间的免密通信原来是这么的简单 何为免密通信&#xff0c;说的大白话就是&#xff0c;我连接你的服务器不需要密码&#xff0c;哈哈&#xff0c;就是所谓的免密通信 今天小编也不讲免密的基本原理了哈&#xff0c;原理的话&#xff0c;百度里面有好多 小编的主要目的呢是…

1.1 半加器

输入1输入2结果进位0000101001101101 半加器: 实现1位的加法 根据结果可知输入1与输入2相加结果 -> 符合 异或门进位 -> 符合 与门最终要么有结果要么有进位,不存在即有结果也有进位 异或门的实现也可以由基本的3个 “与或非” 门实现 与:& , 或:| , 非:! 用这3个…

开通橱窗还能开抖店吗?怎么开通?一篇详解!

我是电商珠珠 开通商品橱窗之后还能开抖店吗&#xff1f;商品橱窗和抖音小店可以同时开吗&#xff1f; 一部分人最初的时候&#xff0c;都觉得直播带货很火&#xff0c;所以就自己去买粉丝或是发视频积攒粉丝&#xff0c;等粉丝够了发现&#xff0c;好像和当初想的不太一样&a…

2017年五一杯数学建模A题公交车排班问题解题全过程文档及程序

2017年五一杯数学建模 A题 公交车排班问题 原题再现 随着徐州市经济的快速发展&#xff0c;公交车系统对于人们的出行扮演着越来越重要的角色。在公交车资源有限的情况下&#xff0c;合理的编排公交车的行车计划成为公交公司亟待解决的问题。以下给出公交车排班问题中的部分名…

Java实现-数据结构 2.时间和空间复杂度

.如何衡量一个算法的好坏&#xff1a;时间复杂度和空间复杂度 算法效率分为时间效率和空间效率&#xff0c;时间效率称为时间复杂度&#xff0c;空间效率称为空间复杂度 时间复杂度 算法的时间复杂度是一个数学函数&#xff0c;它描述了算法的运行时间&#xff0c;一个算法执…

什么是客户自助服务?综合指南献上~

《哈佛商业评论》曾报道过&#xff0c;81%的消费者在找客服之前会自己先去找办法解决。 如今&#xff0c;客户希望得到更快的响应。他们不想排队去等信息。他们想要的只是一个更快、更可靠的自助服务解决方案。作为企业&#xff0c;应该注意到他们的期望。企业需要做的就是通过…

2017年五一杯数学建模C题宜居城市问题值解题全过程文档及程序

2017年五一杯数学建模 C题 宜居城市问题 原题再现 城市宜居性是当前城市科学研究领域的热点议题之一&#xff0c;也是政府和城市居民密切关注的焦点。建设宜居城市已成为现阶段我国城市发展的重要目标,对提升城市居民生活质量、完善城市功能和提高城市运行效率具有重要意义。…

2009年iMac装64位windows7及win10

2009年iMac装64位windows7及win10 Boot Camp没有“创建 Windows7 或更高版本的安装磁盘”选项 安装完Mac OS系统后&#xff0c;要制作Windows7安装U盘时才发现&#xff0c;Boot Camp没有“创建 Windows7 或更高版本的安装磁盘”选项&#xff0c;搜索到文章&#xff1a;修改Boo…

C语言——深入理解指针(2)

目录 1. 数组名 2. 指针访问数组 3. 一维数组的传参&#xff08;本质&#xff09; 4. 冒泡排序 5. 二级指针 6. 指针数组&#xff08;指针的数组&#xff09; 7. 指针数组模拟二维数组 1. 数组名 在之前的代码中我们使用指针访问过数组的内容。 int arr[10] {1,2,3,4…

xxljob学习笔记01(小滴课堂)

分布式调度xxl-job源码部署和数据库建立&#xff1a; 在idea中打开安装包&#xff1a; 创建数据库&#xff1a; 建表&#xff1a; 在项目里&#xff1a; 在navicat里运行语句即可&#xff1a; 修改数据库地址和用户名&#xff0c;密码&#xff1a; 配置令牌&#xff0c;不然谁…

Linux环境下自动化创建大量的账号

参考《鸟哥的Linux私房菜基础篇第四版》13.7.2节微调而成&#xff1a; 下面脚本的目的是为服务器的管理员自动化创建大量的账号&#xff0c;节省生命。 #!/bin/bash # This shell script will create amount of Linux login accounts for you. # 1. check the "accounta…

探索计算机视觉:深度学习与图像识别的融合

探索计算机视觉&#xff1a;深度学习与图像识别的融合 摘 要&#xff1a; 本文将探讨计算机视觉领域中的深度学习技术&#xff0c;并重点关注图像识别方面的应用。我们将介绍卷积神经网络&#xff08;CNN&#xff09;的原理、常用的图像数据集以及图像识别的实际应用场景&…

python环境搭建-yolo代码跑通-呕心沥血制作(告别报错no module named torch)

安装软件 安装过的可以查看有没有添加环境变量 好的! 我们发车! 如果你想方便快捷的跑通大型项目,那么必须安装以下两个软件: 1.pycharm2.anaconda对应作用: pycharm:专门用来跑通python项目的软件,相当于一个编辑器,可以debug调试,可以接受远程链接调试!anaconda:专…

【JavaEE初阶】浅谈进程

✏️✏️✏️今天正式进入JavaEE初阶的学习&#xff0c;给大家分享一下关于进程的一些基础知识。了解这部分内容&#xff0c;只是为后续多线程编程打好基础&#xff0c;因此进程部分的知识&#xff0c;不需要了解更加细节的内容。 清风的CSDN博客 &#x1f61b;&#x1f61b;&a…

Android之高级UI

系统ViewGroup原理解析 常见的布局容器: FrameLayout, LinearLayout,RelativeLayoout,GridLayout 后起之秀&#xff1a;ConstraintLayout,CoordinateLayout Linearlayout Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation …

OpenGL 自学总结

前言&#xff1a; 本人是工作后才接触到的OpenGL&#xff0c;大学找工作的时候其实比较着急&#xff0c;就想着尽快有个着落。工作后才发现自己的兴趣点。同时也能感觉到自己当前的工作有一点温水煮青蛙的意思&#xff0c;很担心自己往后能力跟不上年龄的增长。因此想在工作之余…