【Java多线程进阶】CAS机制

news2024/9/22 23:25:28

前言

CAS指的是Compare-And-Swap(比较与交换),它是一种多线程同步的技术,常用于实现无锁算法,从而提高多线程程序的性能和扩展性。本篇文章具体讲解如何使用 CAS 的机制以及 CAS 机制带来的问题。

目录

1. 什么是CAS?

2. CAS的应用

2.1 实现原子类

2.2 实现自旋锁

3. CAS的ABA问题

3.1 ABA问题可能引起的BUG

3.2 解决ABA问题


1. 什么是CAS?

CAS 全名 compare and swap (比较并交换)是一种基于 Java 实现的 计算机代数系统,用于多线程并发编程时数据在无锁的情况下保证线程安全安全运行。

CAS机制 主要用于对一个变量(操作)进行原子性的操作,它包含三个参数值:需要进行操作的变量A、变量的旧值B、即将要更改的新值C。

CAS机制 会对当前内存中的 A 进行判断看是否等同于 B ,如果相等则把 A 值更改为 C ,否则不进行操作。以下为 CAS 操作的一段伪代码:

        boolean CAS(A,B,C) {
            if (&A == B) {
                &A = C;
                return true;
            }
            return false;
        }

当然,以上代码不具有原子性只是简单理解 CAS 的判定以及返回机制。真正的 CAS 只是一条 CPU 指令,相比于上述代码具有原子性 。

在了解 CAS 的基本判定后下面我们来看如何通过 Java 标准库来运用 CAS 。


2. CAS的应用


2.1 实现原子类

CAS 可以不加锁保证操作的原子性,Java 标准库提供了 Atomic + 包装类,相关的组合类来实现原子操作,这些类都是在 java.util.concurrent.atomic 包底下的。

以常用的 AtomicInteger 类来举例,AtomicInteger 类底下的 getAndIncrement 方法达到的效果就是自增类似于 i++ 操作,getAndDecrement 方法就是自减类似于 i-- 操作。

因此 AtomicInteger 类常见的方法有:

  • getAndIncrement 方法,自增操作,类似于 i++。
  • getAndDecrement 方法,自减操作,类似于 i--。
  • get 方法,获取当前 AtomicInteger 类引用的值。

当然,Atomic + 其他“数值”包装类也能使用以上方法!


代码案例,不使用 synchronized 的情况下保证一个线程自增5000,另一个线程也自增5000,最后返回两线程之和10000:

public static void main(String[] args) throws InterruptedException {
        //初始化number为0
        AtomicInteger number = new AtomicInteger(0);
        //线程1使number自增5000次
        Thread thread1 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                number.getAndIncrement();
            }
        });
        //线程2也使number自增5000次(在线程1执行后)
        Thread thread2 = new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                number.getAndIncrement();
            }
        });
        thread1.start();//启动线程1
        thread2.start();//启动线程2
        thread1.join();//等待线程1执行完毕
        thread2.join();//等下线程2执行完毕
        System.out.println(number.get());//输出number的值
    }

 运行后打印:

以上代码,在不使用锁(synchronized)的情况下保证了线程的安全性。其底层运用的就是 CAS 机制,getAndIncrement 方法的具体实现,我们可以参考以下 伪代码 来理解:

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

假设 getAndIncrement 方法被两个线程同时调用,线程1 和 线程2 的 oldValue 值都为 0,内存中的 value 值为0。

1)线程1 进入了 getAndIncrement 方法,此时线程1进行 CAS 判定,发现线程1的 oldValue = value,就把 value 进行自增。

2) 线程2 进入了 getAndIncrement 方法,此时 线程2 进行 CAS 判定,发现 oldValue != value,进入 while 循环,把 value 赋值给 old Value。

3)经过以上判断后,线程2 再次进行 CAS 判断时,发现 oldValue = value 了,此时的 value 值又会自增。

以上的 伪代码 就能实现一个原子类,里面的 getAndIncrement 方法也是具备原子性的。通过上述图例就能很好的理解。


2.2 实现自旋锁

CAS的自旋锁指的是在使用CAS操作时,当CAS操作失败后,线程不直接阻塞等待,而是继续尝试执行CAS操作,即对前一次CAS操作的失败进行重试,直到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.currentThread() 为当前对象的引用,以上代码进行 CAS 判定时:

  • 如果判断 this.owner 为空,则把当前对象的引用赋值给 this.owner。此时 CAS 方法返回 true,并取反,while 循环退出。
  • 判断 this.owner 不为空,则不做任何操作,CAS 方法返回 false,并取反,while 循环继续执行。由于 while 循环体内没有任何内容,while 条件判断会执行很快,直到 this.owner 加锁成功为止。

这就是自旋锁的体现,关于锁的策略在本专栏中有详细讲解。大家可以前去查找。


3. CAS的ABA问题

ABA 问题是:当线程1首先读取到共享变量值A。然后线程2先把这个共享变量值修改为B,再修改回A。

此时其他线程再进行 CAS 操作时误以为共享变量值没有被修改过,从而成功的将共享变量更改为新值。

但实际过程中共享变量经历了 由 A 变为 B,再由 B 变为 A,这样就可能会导致一些问题。

类似于,网上购买一部二手机。买的时候,卖家说是零件完好,到手后才发现是一部翻新机。这样就会导致手机用不了几天就出问题。至于到手之前,卖家不说是识别不出这部手机的好坏的。


3.1 ABA问题可能引起的BUG

ABA 问题,就是 CAS 机制导致的数据反复横跳。

假设,张三要去 ATM 取钱,张三余额有 1000 元,他要取 500 元。他安排两个线程,线程1 和 线程2 来并发执行取钱操作。

预期效果:线程1 执行取钱操作判断余额为 1000,执行余额 -500 操作,此时余额 500,线程2 处于阻塞等待状态。当 线程2 执行取钱操作判断余额不是 1000 不执行 -500 操作。

ABA问题出现:线程 1 执行取钱操作判断余额为 1000,执行余额 -500 操作,此时余额 500,线程2 阻塞等待状态。突然,张三的朋友给他转账了 500 ,此时 余额又变回了 1000。

线程2 进入取钱操作时,判断余额为 1000 元,执行余额 -500 操作,此时余额剩余 500。这就是 ABA 问题造成的后果,张三回家后打开手机查看余额剩余 500,实际张三被 ABA 问题坑了 500元。


3.2 解决ABA问题

CAS 操作,是将需要改变的值 A 与旧值 B 进行比较,相等则把新值 C 赋值给 A ,否则不做改变。解决 CAS 出现 ABA 问题,我们可以引入一个版本号,比较版本号是否符合预期。

比如在网上购买一部二手机,卖家会将手机的翻新程度进行一个版本号标记,翻新1次记版本号1,翻新2次的记版本号2,以此类推。这时候,客户会根据版本号来选择翻新程度相应的手机。

  • 当版本号和读到的版本号相等,则修改数据,并把版本号 + 1。
  • 当版本号高于读到的版本号,就操作失败(认为数据已经被修改过了)

根据以下 伪代码 来理解:

num = 0;
version = 1;
old = version;
CAS(version,old,old+1,num);

public void CAS(version,oldVersion,oldVersion+1,num){
    if(version == oldVersion) {
        version = oldVersion + 1;
        num++;
    }
}

对以上代码进行一个讲解, version 作为版本号,当 version 版本号等于读到的 oldVersion 版本号,则把 oldVersion +1 赋值给 version,并且 num ++ 。这样就能避免 ABA 问题的出现。


当然,Java 中 提供了一个 AtomicStampedReference<>类,这个类可以对某个类进行保证,这样就能提供上述的版本号管理功能。

public class TestDemo {
    private static final AtomicStampedReference<Integer> sharedValue = new AtomicStampedReference<>(10, 0);
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            int expectedStamp = sharedValue.getStamp();
            int newValue = 20;
            sharedValue.compareAndSet(10, newValue, expectedStamp, expectedStamp + 1);
            System.out.println(Thread.currentThread().getName() + " updated sharedValue to " + newValue);
        }, "Thread-1");

        Thread thread2 = new Thread(() -> {
            int expectedStamp = sharedValue.getStamp();
            int oldValue = sharedValue.getReference();
            int newValue = 30;
            sharedValue.compareAndSet(oldValue, newValue, expectedStamp, expectedStamp + 1);
            System.out.println(Thread.currentThread().getName() + " updated sharedValue to " + newValue);
        }, "Thread-2");

        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        
        System.out.println("final value: " + sharedValue.getReference());
    }
}

运行后打印:

以上代码,共享变量的初始值为10,然后线程1将共享变量的值修改为20,线程2将共享变量的值修改为30。由于AtomicStampedReference类包含版本号信息,因此即使共享变量的值在这个过程中发生了ABA的变化,CAS操作也可以正常进行,不会出现误判现象。


谈谈你对 CAS 机制的理解?

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


ABA 问题如何解决?

我们可以给修改的数据加上一个版本号,初始化当前版本号与旧的版本号相等。判断当前版本号如果等于旧版本号则对数据进行修改,并使版本号自增。判断当前版本号大于旧版本号,则不进行任何操作。


🧑‍💻作者:一只爱打拳的程序猿,Java领域新星创作者,阿里云社区优质创作者、专家博主。

📒博客主页:这是博主的主页 

🗃️文章收录于:Java多线程编程 

🗂️JavaSE的学习:JavaSE 

🗂️Java数据结构:数据结构与算法 

 本篇博文到这里就结束了,感谢点赞、收藏、评论、关注~

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

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

相关文章

《机器学习算法竞赛实战》-chapter4特征工程

《机器学习算法竞赛实战》学习笔记&#xff0c;记录一下自己的刷题过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 特征工程 特征工程是算法竞赛中工作量最大&#xff0c;决定参赛者能否拿到较好名次的关键部分。吴恩达老师说过&#xff1a;“机器学习在本质上还是特…

Maven Compile时报错 [ERROR] [X Group Enforcer Rules] find DuplicateClasses

类冲突 [ERROR] [Ctrip Group Enforcer Rules] find DuplicateClasses Found in: net.jpountz.lz4:lz4:jar:1.3.0:compile org.lz4:lz4-java:jar:1.7.1:compile Duplicate classes: net/jpountz/xxhash/AbstractStreamingXXHash32Java.class net/jpountz/lz4/LZ4BlockInputStre…

Arduino+ESP8266 MCU开发板 ----带你开发 (TCP)双向局域网通信 项目-----可开发成为小远程控制

目录 本次需要下载的代码链接&#xff1a; 文章项目引用&#xff1a;本次实现可依据之前的文章就行举一反三的操作 本次开发项目步1&#xff1a;下载Aeduino软件 本次开发项目步2&#xff1a; 更替代码片段1 第13行 代替账号和密码 更替代码片段2 第20行 …

软件科技项目验收有哪些注意事项?第三方软件验收测试的好处在哪?

软件科技项目的验收是软件生产周期中非常重要的一个环节。它主要目的是验证软件产品是否满足用户需求&#xff0c;达到预期的质量和性能要求。因此&#xff0c;在进行软件科技项目验收时&#xff0c;有一些注意事项需要遵循。 一、软件科技项目验收的注意事项 1、明确验收标准…

JS使用随机数生成随机验证码

一 随机数测试 在JavaScript中&#xff0c;我们可以使用random&#xff08; &#xff09;方法来生成0&#xff5e;1的一个随机数。random&#xff0c;就是“随机”的意思。需要注意的是&#xff0c;这里的0&#xff5e;1包含0但不包含1&#xff0c;也就是[0, 1&#xff09;。在…

ubuntu20.04 ROS 环境下使用velodyne激光雷达

ROS 环境下使用velodyne激光雷达 系统版本:ubuntu 20.04 ROS版本&#xff1a;noetic 激光雷达型号&#xff1a;velodyne VLP-16 1.系统环境配置 sudo apt install ros-noetic-velodyne #安装ROS依赖 mkdir-p velodyne_ws/src …

PyQt重绘事件处理函数paintEvent

PyQt中的重绘和Windows编程中的重绘差不多&#xff0c;但是Qt的重绘更有特色&#xff0c;更加智能。基础部件类QWidget提供的paintEvent函数是一个纯虚函数&#xff0c;继承它的子类想用它&#xff0c;就必须重新实现它。下列4种情况会发生重绘事件&#xff1a; &#xff08;1…

全网最全的Salesforce营销云管理员认证考点梳理!

Marketing Cloud管理员认证适用于在Marketing Cloud套件中具有设置和维护各种模块&#xff08;Studios/Builders&#xff09;和功能的经验的营销专业人员。Salesforce建议备考者拥有3到6个月的Marketing Cloud管理员和数字营销人员实践经验。 营销云管理员认证 考试内容&#…

IOS发布:App Store Connect Operation Error。SDK Version Issue.

错误描述&#xff1a; App Store Connect Operation Error SDK Version Issue. This app was built with the iOS 15.0 SDK. all iOS apps submitted to the App Store must be built with the iOS 16.1 SDK or later, included in Xcode 14.1 or later. 问题原因&#xf…

ESP32 的多种睡眠模式以及如何让 ESP32 进入深度睡眠模式

ESP32 因其 BLE 兼容性功能以及更低的成本而受到广泛关注。它还带有 32 个 GPIO 引脚和一个 32 位双核 CPU。虽然,它提供了很多功能,但在正常使用模式下似乎很耗电。当应用程序由市电供电时,没有急事,但是当它们由电池供电时,我们必须非常注意 ESP32 的功耗。 ESP32 提供…

SQL使用技巧(6)HIVE开窗函数

专题&#xff1a;SQL使用技巧——实践是检验SQL函数的唯一标准 一.构建数据二.排序开窗三.sum开窗&#xff08;重点内容&#xff09;3.1累加与求和3.2窗口表达式3.3场景模拟 四.count开窗4.1计数规则4.2计数与排序 五.max和min开窗六.lead和lag开窗七.first_value和last_value开…

RabbitMq创建交换机和队列

1. 网页登录 IP:1572 2. 输入登录账号密码 admin admin 3. 点击Exchanges 添加交换机Platform_AlarmEngineInterface 和Rg_Platform_AlarmEngineInterface &#xff0c;Type选择topic 4. 添加队列 VIDEO_Alarm_platform、watch_ftp、RG_VIDEO_Alarm_platform、RG_VIDEO_…

远程进入服务器界面黑屏如何处理?

​  远程登录服务器出现黑屏是很常见的问题&#xff0c;可能是由于多种原因引起的。但不管是哪种原因&#xff0c;当远程连接成功&#xff0c;进入桌面显示黑屏都会让用户感到困扰&#xff0c;无法正常使用服务器。下面我们将为您介绍一些常见的解决方法。 1. 重启远程桌面服…

js深拷贝和浅拷贝

&#x1f449;十分钟学会 前端面试题 js 深拷贝与浅拷贝_前端深拷贝和浅拷贝面试题_Mar-30的博客-CSDN博客 目录 背景&#xff1a; 概念&#xff1a;核心是创建新地址 方法&#xff1a; 浅拷贝&#xff1a; Object.assign() 方法&#xff1a;Object.assign(拷贝的对象&am…

k8s证书过期

[rootmaster1 ~]# kubectl get nodes Unable to connect to the server: x509: certificate has expired or is not yet valid: current time 1.master服务器&#xff1a; # 备份 kubernetes配置 cp -r /etc/kubernetes /etc/kubernetes_bak # 检测证书过期 kubeadm certs…

数智荣耀丨美格智能荣登2023年度中国数智转型标杆企业榜

5月31日&#xff0c;2023数智产业领袖峰会在北京召开&#xff0c;本届大会由智次方—物联网智库主办&#xff0c;大会以大模型时代的“破”与“立”为主题&#xff0c;汇聚产业领袖、专家学者、投资机构、创新企业等各界精英&#xff0c;共同探讨企业如何利用新技术实现“破圈”…

【地平线X3M平台点亮sensor出现问题的分析】

转自地平线论坛经验 1. IC通讯失败的问题以及排查方法。 外界的图像数据一般是通过sensor感知&#xff0c;然后通过mipi接口进入到地平线X3M这款AI芯片的视频处理模块&#xff0c;模块对视频图像做比如放大、缩小、旋转等处理。 问题&#xff1a;IC通讯失败&#xff0c;错误…

手机MT4平台怎么样?手机版MT4平台使用教程教学

外汇是一个全球化的去中心化交易市场&#xff0c;与股票这样只提供一种报价的集中交易不同&#xff0c;在外汇市场中&#xff0c;货币的报价都不是单一的&#xff0c;意味着不同的外汇交易平台的报价会有所不同。以前外汇交易散户是很难进入市场的&#xff0c;参与者主要是银行…

功能强大UI美观的视频答题猜歌闯关娱乐微信小程序源码

正文&#xff1a; 功能强大UI美观的视频答题猜歌闯关娱乐微信小程序源码下载 后台管理资源本地化带数据和视频教程&#xff0c;这是一款拥有后端的闯关娱乐小程序。支持个人小程序和企业小程序上线运营功能强大齐全,带数据本地化(数据在自己服务器自己管理无需担心第三方失效…

【TCP 协议3】提高效率的五大机制

文章目录 前言一、滑动窗口与高速重传1, 什么是滑动窗口2, 什么是高速重传2.1, ack 丢包2.2, 数据丢包 二、流量控制1, 什么是流量控制 三、拥塞控制1, 什么是拥塞控制 四、延迟应答1, 什么是延迟应答 五、捎带应答1, 什么是捎带应答 总结 前言 各位读者好, 我是小陈, 这是我的…