java EE 初阶 — CAS 的介绍

news2025/1/21 9:30:14

文章目录

  • CAS
    • 1. 什么是 CAS
    • 2. CAS 是怎么实现的
    • 3. CAS 有哪些应用
      • 3.1 实现原子类
      • 3.2 实现自旋锁
    • 4. CAS 的 ABA 问题
      • 4.1 什么是 ABA 问题
      • 4.2 ABA 问题引来的 BUG
      • 4.3 解决方案
    • 5. 相关面试题

CAS

1. 什么是 CAS


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

一个 CAS 涉及到以下操作:

我们假设内存中的 原数据V旧的预期值A,需要修改的新值B

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

举个例子。

2. CAS 是怎么实现的


此处最特别的地方,上述这个 CAS 的过程并非是通过一段代码实现的,
而是通过一条 CPU 指令完成的。

因为 CAS 操作是原子的,所以就可以在一定程度上回避线程安全问题。

解决线程安全问题,除了加锁之外,又有了一个新的方向了。

CAS 可以理解为是 CPU 给咱们提供的一个特殊指令,通过这个指令,就可以一定程度上处理线程安全问题。

观察下面一段伪代码(只是辅助理解CAS 的工作流程)

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


address 就相当于是上面例子的 V,expectValue 相当于是 A
swapValue 相当于是 B。

3. CAS 有哪些应用

3.1 实现原子类


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

典型的就是 AtomicInteger 类。
其中:
getAndIncrement 相当于 i++ 操作,
incrementAndGet 相当于是 ++i 操作。
getAndDecrement 相当于是 i-- 操作,
decrementAndGet 相当于是 --i 操作。

package thread;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadDemo8 {

    public static void main(String[] args) throws InterruptedException{
        // 这些原子类,就是基于 CAS 实现了自增,自减操作,此时进行这类操作不加锁,也是线程安全的
        AtomicInteger count = new AtomicInteger(0);

        // 使用原子类来解决线程安全问题
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();// 相当于是 count++
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                count.getAndIncrement();// 相当于是 count++
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println(count.get());
    }
}


因为进行这类操作不加锁是,也是线程安全的,所以代码会输出 10000。



针对上面 count++ 操作的伪代码实现:

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


代码分析

箭头从上到下代表执行的顺序。



1、t1 先执行 load ,把 value 的值读到 oldvalue 中。

读取后




2、执行 t2 的 load 操作,把 value 的值 读到 oldvalue 中

执行后




3、t2 执行 CAS 操作比较 oldvalue 和 value 的值是否相等

此时 value 与 oldvalue 的值相等,就将 oldValue+1 的值读给 value。

读取后




4、t1 执行 CAS 操作比较 oldvalue 和 value 的值是否相等

此时 value 与 oldvalue 的值不相等,CAS 返回 false,并且不进行任何交换。

  while ( CAS(value, oldValue, oldValue+1) != true) {
      oldValue = value;
  }

针对于上述的代码,CAS 返回 false 后,进入 while 循环,将 value 的值 读给 oldvalue。

紧接着 t1 就要再接着执行一次 load 和 CAS。

读取后。




5、t1 执行 CAS 操作比较 oldvalue 和 value 的值是否相等

此时 value 与 oldvalue 的值相等,就将 oldValue+1 的值读给 value。

读取后



此时比较相等,CAS 返回 true ,循环就结束了。

上面的伪代码要结合下面的图示来理解。

3.2 实现自旋锁


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


this.owner, null 监测当前的 owner 是否是 null ,如果是 null就进行交换,
也就是把当前线程的引用赋值给 owner 。
如果赋值成功,此时循环结束,加锁完成了。

如果当前锁已经被别的线程占用了,CAS 就会发现 this.owner 不是 null。
CAS 就不会产生赋值,也同时返回 false ,循环继续执行,并且下次判定。

4. CAS 的 ABA 问题

4.1 什么是 ABA 问题


CAS 在运行中的核心,检查 value 和 oldvalue 是否一致。
如果一致就视为 value 中途没有被修改过,所以进行下一步操作是没有问题的。

这里指的一致,可能是没有改过,也可能改过之后又还原回来了。

比方说,把 value 的值设为 A 的话,CAS 判定 value 为 A 此时可能确实 value 始终是 A。
也可能是 value 本来是 A ,被改成了 B 后又改回了 A 。

举个例子。

就像是买手机,我买到的这个手机,可能是翻新机,也可能是新机。
不管是哪一种,我都无法区分。

4.2 ABA 问题引来的 BUG


ABA 这个情况大部分情况下其实是不会对代码逻辑产生太大影响的。

但是不排除一些比较极端情况,也是可能造成影响的。

下面举一个在实际开发环境中发生概率不大的例子

假设当前滑稽老铁的账户余额 为 1000 ,滑稽准备去 500 。

当按下取款这一瞬间的时候,机器卡了一下,滑稽老铁忍不住就多按了几下。
这是可能就会产生 bug ,可能就会触发重复扣款的操作。

考虑使用 CAS 的方式来扣款。



t1 和 t2 都执行 load 操作,都读取到了 1000 这个值。
紧接着 t1 执行 CAS 比较一下,看看此时的余额是不是 1000,如果是 1000 就扣 500。

当 t2 执行 CAS 操作的时候,滑稽的余额为 500 不等于 1000,此时扣款不成功。

如果在执行果第一个 CAS 后,有人给滑稽转了 500 元,此时余额又变成了 1000。
这就相当于,本来是 1000 变成 500 后 又变成了 1000 ,这就是一个ABA 问题。



此时的余额是 1000 ,就又扣款成功了,这里就出现 bug 了。

4.3 解决方案


针对当前的问题,采取的方案就是 加一个版本号

比方说,初始版本号是 1 ,每次修改版本号都加1,然后进行 CAS 的时候,不是以金额为准了,而是以版本号为准。

版本号真是增长的,不能降低。此时只要是版本号没变,就是一定没有发生改变。

5. 相关面试题


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

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


2、ABA问题怎么解决

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式——工厂方法模式

文章目录1. 工厂方法模式的定义2. 工厂方法模式的类图3. 工厂方法模式的作用4. 工厂方法模式的实现1. 工厂方法模式的定义 定义了一个创建对象的接口&#xff0c;但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。 2. 工厂方法模式的类图 3. 工厂方法模式…

[教程]一文搞懂STM32使用DHT11采集温湿度

1、DHT11简介 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术&#xff0c;确保产品具有极高 的可靠性与卓越的长期稳定性。传感器包括一个电阻式感湿元件和一个NTC测 温元件&#xff0c;并与一个高性能8…

GO语言基础-08-内建函数-make()、new()

文章目录1. make1.1 概述1.2 示例&#xff08;make切片&#xff09;1.3 示例&#xff08;make map&#xff09;1.4 示例&#xff08;make 通道&#xff09;2. new2.1 概念2.2 示例&#xff08;new 切片&#xff09;2.3 示例&#xff08;new和make对比&#xff09;2.4 示例&…

Java基础算法每日5道详解(2)

83. Remove Duplicates from Sorted List 从排序列表中删除重复项 Given the head of a sorted linked list, delete all duplicates such that each element appears only once. Return the linked list sorted as well. Example 1: Input: head [1,1,2] Output: [1,2]Exa…

20230109测试ToyBrick的RK3588开发板运行Buildroot的V20220811版本

20230109测试ToyBrick的RK3588开发板运行Buildroot的V20220811版本 2023/1/9 14:25 开发板&#xff1a;Toybrick的TB-RK3588X开发板 SDK&#xff1a;RK3588_LINUX_20220811\rk3588-linux-20220811.tar.gz_06 H:\BaiduNetdiskDownload\RK3588_LINUX_20220811 rk3588-linux-2022…

【SQLyog错误号码2058解决办法】

当你遇到下图这个错误时&#xff0c;是由于SQLyog在8.0以上版本采用了新的加密方式。 解决办法&#xff1a; win R打开 &#xff0c; 输入cmd&#xff0c;打开命令行窗口&#xff0c; 然后连接你的SQLyog版本的服务器&#xff0c; mysql -uroot -P3306 -p注意&#xff1a;…

【Kotlin】数字类型 ( 安全转换函数 | 浮点型转整型 )

文章目录一、安全转换函数二、浮点型转整型一、安全转换函数 在 Kotlin 中 , 将 字符串 String 类型 转为 数字类型 , 如果 字符串 代表的数字类型 与 要换转的 数字类型 不匹配 , 就会出异常 ; 如 : 执行如下代码 , 就会报异常 ; 字符串内容是 0.5 , 显然是一个 Double 类…

Kotlin Flow响应式编程,StateFlow和SharedFlow

本文同步发表于我的微信公众号&#xff0c;扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注&#xff0c;每个工作日都有文章更新。 大家好&#xff0c;今天是Kotlin Flow响应式编程三部曲的最后一篇。 其实回想一下我写这个Kotlin Flow三部曲的初衷&#xff0c;主要还是因为…

基于瑞芯微平台cif接口dvp相机的视频接入(ov2640、rv1126为例)

基于瑞芯微平台cif接口dvp相机的视频接入&#xff08;ov2640、rv1126为例&#xff09;名词定义视频格式sensor与ispI2CXCLK行场同步信号DATA抓图名词定义 CIF&#xff0c;指RK芯片中的VIP模块&#xff0c;用以接收Sensor数据并保存到Memory中&#xff0c;仅转存数据&#xff0c…

Komo 综合资产收集和漏洞扫描工具

前言 因工作中的需要&#xff0c;开发了这款综合资产收集和漏洞扫描工具&#xff0c;方便在工作中各方面的收集资产和漏洞扫描&#xff0c;同时也可用于挖洞。 Komo已经在工作中辅助我挖到过一些漏洞&#xff0c;同时轻便了我资产收集的过程。 Komo is a comprehensive asset c…

【图像处理OpenCV(C++版)】——3.3 几何变换之极坐标变换

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

MATLAB | 如何从热图中提取数据

这期做了个可能有用的小工具&#xff0c;一般论文中热图很少给出数据&#xff0c;于是就想写个小工具通过热图上的颜色估计出数据值来&#xff0c;目前写了个初版的工具分享给大家&#xff01; 工具函数 由于只是初版&#xff0c;要手动改的地方还是不少的&#xff0c;要设置…

PHP多商户AI智能在线客服系统源码 机器人自动回复 即时通讯聊天系统源码

一套智能在线客服系统源码 多商户网页客服系统源码 支持二十种国际语言 带机器人自动回复。 框架&#xff1a;Thinkphp5workerman&#xff0c; 环境&#xff1a;nginxphp7.3mysql5.6 支持H5公众号APP小程序 了解更多可私信我&#xff01; 系统功能&#xff1a; 1、支持国际…

编写程序时调用第三方程序时使用的是相对路径而不是绝对路径会造成什么严重后果(Windows Linux)

简介 在编写程序时&#xff0c;有很多人调用第三方程序使用的是相对路径&#xff0c;而不是绝对路径&#xff0c;如下&#xff1a; #!/bin/python3import osos.system("whoami") #调用whoami程序&#xff0c;查看当前用户名#!/bin/bashfind / -name "hellowor…

day10|239. 滑动窗口最大值、347.前 K 个高频元素

239. 滑动窗口最大值 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3…

如何修改视频MD5的格式?这些方法值得你收藏

MD5实际上是计算机安全领域中广泛使用的一种散列函数&#xff0c;可以用来保护消息的完整性&#xff0c;简单来说就是类似于我们的指纹&#xff0c;可以说MD5是每个文件的“数字指纹”。比如&#xff1a;我们在平台上传一些热门视频&#xff0c;平台会自动识别视频的MD5值&…

嵌入式 LINUX 驱动开发 day01 第一个内核模块程序 多文件编译为一个程序, 内核模块参数, 内核模块依赖

1.第一个内核模块程序 ( 记得配置自己的交叉编译的工具,) 首先两个文件 vser.c Makefile (记得大写的M) vser.c #include <linux/init.h> //内核初始化头文件 #include <linux/module.h> //内核模块文件 #include <linux/kernel.h> //&…

Java基础算法每日5道详解(6)

112. Path Sum 路径总和 Given the root of a binary tree and an integer targetSum, return true if the tree has a root-to-leaf path such that adding up all the values along the path equals targetSum. A leaf is a node with no children. Example 1: Input: ro…

html+css实现一个响应式管理平台架构模板

文本将会带你使用htmlcss实现一个响应式的管理平台架构模板&#xff0c;目前来说市面上的管理平台架构模板大同小异&#xff0c;文本的知识点都会符合场景所需。 目录 1、管理平台的架构内容 2、顶部的布局 3、下半部分布局 4、左侧菜单区域实现 5、右侧主体区域实现 …

前端重新部署如何通知用户刷新网页?

我把我掘金的文章同步一份给CSDN 1.目标场景 有时候上完线&#xff0c;用户还停留在老的页面&#xff0c;用户不知道网页重新部署了&#xff0c;跳转页面的时候有时候js连接hash变了导致报错跳不过去&#xff0c;并且用户体验不到新功能。 2.思考解决方案 如何去解决这个问…