CAS(compare and swap)

文章目录

  • CAS 的应用
    • 标准库的原子类
    • 自旋锁
  • CAS的ABA问题
    • 什么是 ABA 问题
    • ABA 问题引来的 BUG
    • 相关面试题

CAS是一条CPU指令,就可以完成比较和交换这样的操作
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
1.比较 A 与 V 是否相等。(比较)
2. 如果比较相等,将 B 写入 V。(交换)
3. 返回操作是否成功。

这里说的交换,实际上更多用来赋值(把寄存器的值交换到内存中)

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

CAS 可以视为是⼀种乐观锁.(或者可以理解成 CAS 是乐观锁的⼀种实现方式)

CAS 的应用

由于CPU提供了上述指令,操作系统内核也就能够完成上述操作,就会提供这样的CAS的API
JVM又对系统的CAS的API进一步封装在了"unsafe"包里,这样在Java代码中就可以使用CAS操作了

标准库的原子类

标准库提供了 java.util.concurrent.atomic 包, 里面的类都是基于CAS的方式来实现的.
典型的就是 AtomicInteger 类. 其中的 getAndIncrement 相当于 i++ 操作

import java.util.concurrent.atomic.AtomicInteger;

public class Test {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                //count++
                count.getAndIncrement();
                //++count
                //count.incrementAndGet();
                //count--
                //count.getAndDecrement();
                //--count
                //count.decrementAndGet();
                //count += 10
                //count.getAndAdd(10);
            }
        });
        Thread t2 = new Thread(()-> {
            for (int i = 0; i < 50000; i++) {
                //count++
                count.getAndIncrement();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + count);
    }
}

在这里插入图片描述
此处我们代码中没有用到任何的加锁操作,这样的方式使代码以更高的效率来执行程序
这一套基于CAS不加锁来实现线程安全代码的方式,也称为"无锁编程"

虽然挺好用的但是适用范围没有锁更广泛,只能针对一些特殊场景

getAndIncrement其实是调用unsafe包里的方法

在这里插入图片描述

getAndIncrement的伪代码实现:

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

假设两个线程同时调用 getAndIncrement

  1. 两个线程都读取 value 的值到 oldValue 中(oldValue 是⼀个局部变量, 在栈上。每个线程有自己的栈)
  2. 假设线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值.

注意:
• CAS 是直接读写内存的, 而不是操作寄存器.
• CAS 的读内存, 比较, 写内存操作是⼀条硬件指令, 是原子的.

  1. 线程2 再执行 CAS 操作, 第⼀次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环.在循环里重新读取 value 的值赋给 oldValue

  2. 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作.

  3. 线程1 和 线程2 返回各自的 oldValue 的值即可.

通过形如上述代码就可以实现⼀个原子类. 不需要使用重量级锁, 就可以高效的完成多线程的自增操作.
本来 比较和交换 这样的操作在代码角度不是原子的. 但是在硬件层面上可以让⼀条指令完成这个操作, 也就变成原子的了.

自旋锁

自旋锁伪代码:

	public class SpinLock {
        private Thread owner = null;//表示持有锁的线程是谁,未加锁状态就是null
        public void lock(){
            // 通过 CAS 看当前锁是否被某个线程持有. 
            // 如果这个锁已经被别的线程持有, 那么就⾃旋等待. 
            // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. 
            while(!CAS(this.owner, null, Thread.currentThread())){
            }
        }
        public void unlock (){
            this.owner = null;
        }
    }

CAS的ABA问题

什么是 ABA 问题

CAS的机制是"比较-发现相等-交换"

假设存在两个线程 t1 和 t2. 有⼀个共享变量 num, 初始值为 A.
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要

  • 先读取 num 的值, 记录到 oldNum 变量中.
  • 使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z.

但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A (A->B->A)
线程 t1 的 CAS 期望 num 不变. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这个时候 t1 究竟是否要更新 num 的值为 Z 呢?

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

ABA 问题引来的 BUG

大部分的情况下, t2 线程这样的⼀个反复横跳改动, 对于 t1 是否修改 num 是没有影响的. 但是不排除⼀些特殊情况.

比如说余额有1000元,去ATM取500元,假设此处取款的操作是按照CAS的方式执行的。

	//伪代码,balance是账户余额
	void 取款() {
        int oldBalance = balance;
        
        if (!CAS(balance, oldBalance, oldBalance-500)) {
            
        }
    }

机器出bug了,按了一下没反应,又按了一下,这样就有两个线程去取款,按照正常情况,第一个取款成功,第二个因为余额(balance)变成500条件判定不是true就不会执行

但是如果这两个取款操作之间有人汇款了500元,这样第二个取款操作中余额还是1000元,就会进入if语句执行扣款操作,相当于取了两次500元

解决ABA问题的核心思路是引入"版本号"
使用账户余额判定就不太科学,账户余额"能加也能减",就容易出现ABA问题
我们约定版本号只能加不能减,每操作一次余额,版本号都要+1,如果版本号没有发生改变,数据就一定没有变过

	//伪代码
	void 取款() {
        int oldVersion = version;

        if(CAS(version, oldVersion, oldVersion+1)) {
            balance -= 500;
        }
    }

给余额搭配⼀个版本号, 初始设为 1.

  1. 线程1 执行扣款成功, 余额被改成 500, 版本号改为2. 线程2 阻塞等待中.
  2. 在线程2 执行之前, 正好转账 500, 账户余额变成 1000, 版本号变成3.
  3. 轮到线程2 执行了, 发现当前存款为 1000, 和之前读到的 1000 相同, 但是当前版本号为 3, 之前读到的版本号为 1,认为操作失败.

在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能.

相关面试题

  1. 讲解下你自己理解的 CAS 机制

全称 Compare and swap, 即 “比较并交换”. 相当于通过⼀个原子的操作, 同时完成 “读取内存,比较是否相等, 修改内存” 这三个步骤. 本质上需要 CPU 指令的支撑.

  1. ABA问题怎么解决?

给要修改的数据引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期. 如果发现当前版本号和之前读到的版本号⼀致, 就真正执行修改操作, 并让版本号自增; 如果发现当前版本号比之前读到的版本号大, 就认为操作失败

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

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

相关文章

2024年7月4日 (周四) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 《最终幻想14》画面升级后 著名…

【高级篇】第10章 Elasticsearch 集群管理与扩展

在本章中,我们将深入探讨Elasticsearch集群的管理与扩展策略,旨在帮助读者构建一个既能应对大规模数据处理需求,又能保持高可用性和弹性的系统架构。我们将从集群架构设计入手,解析不同节点的角色与配置,然后转向节点发现与配置同步机制,最后讨论水平扩展与容错策略,确保…

【Python实战因果推断】20_线性回归的不合理效果10

目录 Neutral Controls Noise Inducing Control Feature Selection: A Bias-Variance Trade-Off Neutral Controls 现在&#xff0c;您可能已经对回归如何调整混杂变量有了一定的了解。如果您想知道干预 T 对 Y 的影响&#xff0c;同时调整混杂变量 X&#xff0c;您所要做的…

系统提示我未定义与 ‘double‘ 类型的输入参数相对应的函数 ‘finverse‘,如何解决?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

新火种AI|AI搜索挑战百度谷歌,重塑信息检索的市场?

作者&#xff1a;一号 编辑&#xff1a;美美 AI正在颠覆传统的搜索引擎市场。 随着ChatGPT等大型语言模型的火爆&#xff0c;AI搜索技术成为了公众和业界关注的焦点。这些技术不仅能够提供快速、准确的信息检索&#xff0c;还能够通过自然语言处理技术理解用户的复杂查询&am…

步进电机(STM32+28BYJ-48)

一、简介 步进电动机&#xff08;stepping motor&#xff09;把电脉冲信号变换成角位移以控制转子转动的执行机构。在自动控制装置中作为执行器。每输入一个脉冲信号&#xff0c;步进电动机前进一步&#xff0c;故又称脉冲电动机。步进电动机多用于数字式计算机的外部设备&…

vue的学习--day3

1、尝试使用json文件模拟增删改查 json server:准备一份自己的数据&#xff08;这里我用的是老师给的&#xff09;。 转到d盘&#xff0c;然后打开json文件&#xff1a; 下面模拟增删改查&#xff1a; 借助工具postman或apifox或apipost&#xff1a; 这里我下载了apifox&…

养老院生活管理系统

摘要 随着全球范围内人口老龄化趋势的日益加剧&#xff0c;养老院作为老年人生活的重要场所&#xff0c;其生活管理问题也显得愈发突出和重要。为了满足养老院在日常生活管理、老人健康监护、服务人员管理等多方面的需求&#xff0c;提高管理效率和服务质量。决定设计并实现了…

鸿蒙小案例-自定义键盘

一个自定义键盘 效果 完成简单的26键中英文输入 使用&#xff1a; Entry Component struct IndexInput {State text: string inputController: TextInputController new TextInputController()//自定义键盘关闭事件hideClick(){this.inputController.stopEditing()}//自定义…

Java SpringBoot MongoPlus 使用MyBatisPlus的方式,优雅的操作MongoDB

Java SpringBoot MongoPlus 使用MyBatisPlus的方式&#xff0c;优雅的操作MongoDB 介绍特性安装新建SpringBoot工程引入依赖配置文件 使用新建实体类创建Service测试类进行测试新增方法查询方法 官方网站获取本项目案例代码 介绍 Mongo-Plus&#xff08;简称 MP&#xff09;是一…

Windows 11 安装 Python 3.11 完整教程

Windows 11 安装 Python 3.11 完整教程 一、安装包安装 1. 下载 Python 3.11 安装包 打开浏览器,访问 Python 官方下载页面。点击“Download Python 3.11”,下载适用于 Windows 的安装包(Windows installer)。 2. 安装 Python 3.11 运行下载的安装包 python-3.11.x-amd6…

(论文版)深度学习 | 基于 VGG16-UNet 语义分割模型的猫狗图像提取研究

Hi&#xff0c;大家好&#xff0c;我是半亩花海。本实验本项目基于 VGG16-UNet 架构&#xff0c;利用 Labelme 标注数据和迁移学习&#xff0c;构建高效准确的猫狗图像分割模型。通过编码器-解码器结构&#xff08;特征提取-上采样&#xff09;提升分割精度&#xff0c;适应不同…

Spring学习03-[Spring容器核心技术IOC学习进阶]

IOC学习进阶 Order使用Order改变注入顺序实现Ordered接口&#xff0c;重写getOrder方法来改变自动注入顺序 DependsOn使用 Lazy全局设置-设置所有bean启动时候懒加载 Scopebean是单例的&#xff0c;会不会有线程安全问题 Order 可以改变自动注入的顺序 比如有个animal的接口&a…

爬虫-豆瓣电影排行榜

获取数据 requests库 获取数据环节需要用到requests库。安装方式也简单 pip install requests 爬取页面豆瓣读书 Top 250 用requests库来访问 import requests res requests.get(https://book.douban.com/top250/) 解析&#xff1a; 导入requests库调用了requests库中的…

网络IO模型之多路复用器.md

多路复用是什么&#xff1f;怎么理解&#xff1f; 本文主要涉及为 程序中处理网络IO时的模型&#xff0c;对于系统内核而言网络IO模型。这里只做普及使用 前置知识&#xff0c;什么是IO&#xff1f;怎么理解IO IO其实就是In和Out。中文翻译是输入和输出&#xff0c;只要涉及到输…

SQL二次注入原理分析

二次注入在测试的时候比较少见&#xff0c;或者说很难被测出来&#xff0c;因为测的时候首先要去找注入的位置&#xff0c;其次是去判断第一次执行的SQL语句&#xff0c;然后还要去判断第二次进行调用的 SQL 语句。而关键问题就出在第二次的调用上面。 下面以一个常用过滤方法…

mmaction2版本适配(Linux)

从cuda到mmcv保姆式教程 &#xff08;数十年踩坑经验&#xff0c;跟着我做&#xff0c;版本不会错~&#xff09; 如果有补充&#xff0c;请评论区评论&#xff0c;后续填坑&#xff01; cuda11.3 下载安装包 wget https://developer.download.nvidia.com/compute/cuda/11.3…

【Linux】多线程(互斥 同步)

我们在上一节多线程提到没有任何保护措施的抢票是会造成数据不一致的问题的。 那我们怎么办&#xff1f; 答案就是进行加锁。 目录 加锁&#xff1a;认识锁和接口&#xff1a;初始化&#xff1a;加锁 && 解锁&#xff1a;全局的方式&#xff1a;局部的方式&#xff1a…

go 学习 之 HTTP微服务示例

1. 背景 学习ing 2. 创建文件&#xff1a;server.go go package mainimport ("github.com/gogf/gf/contrib/registry/file/v2""github.com/gogf/gf/v2/frame/g""github.com/gogf/gf/v2/net/ghttp""github.com/gogf/gf/v2/net/gsvc"&…

Mac 运行 Windows 软件,Parallels Desktop 19和 CrossOver 24全面对比

Parallels Desktop 和 CrossOver 都是能满足你「在 Mac 上运行 Windows 软件」需求的工具。可能很多人都已经知道 Parallels Desktop 是「虚拟机」&#xff0c;但 CrossOver 其实并不是「虚拟机」。这两款软件有相同的作用&#xff0c;但由于实现原理的不同&#xff0c;两者也有…