当前位置: 首页 > news >正文

【Java多线程进阶】CAS机制

前言

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为0AtomicInteger 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();//启动线程1thread2.start();//启动线程2thread1.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数据结构:数据结构与算法 

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

相关文章:

【Java多线程进阶】CAS机制

前言 CAS指的是Compare-And-Swap&#xff08;比较与交换&#xff09;&#xff0c;它是一种多线程同步的技术&#xff0c;常用于实现无锁算法&#xff0c;从而提高多线程程序的性能和扩展性。本篇文章具体讲解如何使用 CAS 的机制以及 CAS 机制带来的问题。 目录 1. 什么是CAS&…...

flex布局总结

flex布局总结 总结自&#xff1a;https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 内容&#xff1a; flex意思是-弹性布局&#xff0c;可以为盒型模型提供极大的灵活性&#xff0c;设置为flex布局后&#xff0c;子元素的fload clear vertical会失效 概念&#x…...

2023 Idea 热部署 JRebel 插件激活方法

2023 Idea 热部署 JRebel 插件激活方法 1. 下载源代码 进入下面 github 地址 clone 代码到本地 https://github.com/Byron4j/JrebelLicenseServerforJava 2. 编译和打包 cd /Users/daixiaohu/Desktop/JrebelLicenseServerforJavamvn clean package3. 运行项目 cd target/jav…...

Java (韩老师课程)第三章

变量的介绍 * 变量是程序的基本组成单位 * 变量相当于内存中一个数据存储空间的表示 * 变量在该区域有自己的名称和类型 * 变量必须先声明&#xff0c;后使用&#xff0c;即顺序 * 变量在该区域的数据/值可以在同一类型内不断变化 * 变量在同一个作用域中不能重…...

【P38】JMeter 随机控制器(Random Controller)

文章目录 一、随机控制器&#xff08;Random Controller&#xff09;参数说明二、测试计划设计2.1、测试计划一2.2、测试计划二2.3、勾选忽略子控制器块 一、随机控制器&#xff08;Random Controller&#xff09;参数说明 可以让控制器内部的逻辑随机执行一个&#xff0c;一般…...

API电商 ERP 数据管理

没有 API&#xff0c;应用之间的通信将会被扼杀&#xff1b;软件开发者将不断重写并执行相同功能的软件&#xff1b;创新的脚步将会放缓。 API 随处可见。大到一个软件系统&#xff0c;小到几行程序&#xff0c;只要具备了一定的特征&#xff0c;都可以被称作 API。那么&#…...

【SQLAlchemy】第四篇——事务

可以把事务理解为一系列操作的集合&#xff1a;这些操作要么全部执行&#xff0c;要么一个也不执行——这样就可以保证数据的一致性和可靠性。在执行更新和删除操作时&#xff0c;尤其要注意利用事务的这个特征。 SQLAlchemy中提供了许多方法来利用事务。 1、如何确保操作生效…...

浅谈QMap中erase与remove的区别

QMap中erase与remove的区别 QMap中erase与remove的区别分别使用erase和remove删除元素使用erase删除元素使用remove删除元素代码讲解 QMap中erase与remove的区别 在实践中发现erase删除元素之后&#xff0c;其迭代器自动指向下一个元素&#xff0c;而remove删除元素之后迭代器…...

FastThreadLocal 原理解析

FastThreadLocal 每个 FastThread 包含一个 FastThreadLocalMap&#xff0c;每个 FastThreadLocalThread 中的多个 FastThreadLocal 占用不同的索引。每个 InternalThreadLocalMap 的第一个元素保存了所有的 ThreadLocal 对象。之后的元素保存了每个 ThreadLocal 对应的 value …...

设计模式B站学习(一)(java)

这里写目录标题 一、设计模式概述1.1 软件设计模式的产生背景1.2 软件设计模式的概念1.3 学习设计模式的必要性1.4 设计模式分类 二、UML图2.1 类图概述2.2 类图的作用2.3 类图表示法2.3.1 类图表示方法2.3.2 类与类之间关系的表示方法2.3.2.1 关联关系2.3.2.2 聚合关系2.3.2.3…...

Pandas如何轻松按位置删除多重索引列?

在Pandas处理DataFrame数据的过程中&#xff0c;我们常常需要删除某些不需要的列。那么&#xff0c;如何高效地按位置删除Pandas DataFrame的多重索引列呢? 今天分享在Pandas中按位置删除多重索引列的具体方法: 第一步:获取所有列标签 使用df.columns获取DataFrame的所有列标…...

第五十七天学习记录:C语言进阶:结构体链表的自学

先展示一段代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS 1#include <stdio.h> #include <stdlib.h>// 定义链表节点结构体 typedef struct Node {int value;struct Node* next; } Node;int main() {// 创建链表头指针Node* head (Node*)malloc(sizeof(Node…...

【一次调频】考虑储能电池参与一次调频技术经济模型的容量配置方法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

ICV报告: 智能座舱SoC全球市场规模预计2025年突破50亿美元

在智能化、互联化车辆需求不断增加的推动下&#xff0c;汽车行业正在经历一场范式转变。这一转变的前沿之一是智能座舱SoC。本市场研究报告对智能座舱SoC市场进行了全面的分析&#xff0c;包括其应用领域、当前状况和主要行业参与者。 智能座舱SoC指的是现代汽车智能座舱系统的…...

在can协议的基础下编写DBC文件,然后使用该DBC文件下发can协议到底盘完整流程

目录 前言一、VectorCANdb下载及安装二、DBC文件的编写1.新建dbc文件2.建立dbc2.1根据CAN协议设置以下的signals2.2设置报文2.3建立报文与信号的关系2.4建立节点 三、编写程序使用UDP通信下发can协议1.查看can口、电脑ip以及端口号2.编写测试程序 前言 最近完成了一个项目&…...

工业传感器有哪些?

工业传感器是指能在工业制造过程能将感受的力、热、光、磁、声、湿、电、环境等被测量转换成电信号输出的器件与装置&#xff0c;在各种化工、机械、汽车等工业场景上都有应用。 工业传感器有哪些&#xff1f; 工业传感器由于不同的特性也被分为多种不同的类别&#xff0c;主要…...

Docker应用部署之Nginx

部署nginx 要求&#xff1a;在docker容器中部署nginx&#xff0c;并通过外部机器访问nginx 步骤&#xff1a; 1.搜索nginx镜像 docker search nginx 2.拉取nginx镜像 docker pull nginx 3.创建容器 #在root目录下创建nginx目录用于存放nginx项目 mkdir ~/nginx cd ~/ng…...

TerminalWorks TSPrint/TSScan/TSWebCam Crack

/ 远程桌面打印软件&#xff0c;TerminalWorks TSPrint Server/Client 从远程服务器打印到本地打印机的 简单方法 TSPrint 为您提供了一个简单的远程桌面打印软件&#xff0c;以及使 Windows 终端服务操作更容易的附加工具。有选择地启用或禁用功能&#xff0c;以便您可以完全…...

如何使用Springboot实现文件上传和下载,并为其添加实时进度条的功能

文件上传和下载是Web开发中非常基础的功能&#xff0c;但在实际开发中&#xff0c;我们经常需要实时显示文件上传或下载的进度。这篇文章将介绍如何使用Springboot实现文件上传和下载&#xff0c;并为其添加实时进度条的功能。 文件上传 添加Maven依赖项 首先&#xff0c;我…...

安装并新建windows下wxwroks7.0 bootrom工程

双击steup.exe 直接next 直接next 选择typical&#xff0c;然后next I accept 安装完成finish 现在双击Workbench 4&#xff0c;新建vxworks7.0工程&#xff0c;会出现下面的情况&#xff0c;因为没有licence 安装licence&#xff0c;将zwrsLicense-vx7-perm.lic粘贴到安装目…...

element-ui表格el-table的使用

先给大家展示一下效果 Table 属性 属性名说明类型可选值默认值data显示的数据array——heightTable 的高度&#xff0c; 默认为自动高度。 如果 height 为 number 类型&#xff0c;单位 px&#xff1b;如果 height 为 string 类型&#xff0c;则这个高度会设置为 Table 的 sty…...

Backtrader官方中文文档:第八章Indicators指标

本文档参考backtrader官方文档,是官方文档的完整中文翻译,可作为backtrader中文教程、backtrader中文参考手册、backtrader中文开发手册、backtrader入门资料使用。 Indicators指标章节目录 指标(Indicator)指标的使用__init__ 对比 next指标在`__init__`阶段的执行过程指标在…...

CAP原则

CAP原则又称CAP定理&#xff0c;指的是在一个分布式系统中&#xff0c;存在Consistency&#xff08;一致性&#xff09;、Availability&#xff08;可用性&#xff09;、Partition tolerance&#xff08;分区容错性&#xff09;&#xff0c;三者不可同时保证&#xff0c;最多只…...

【PowerQuery】M语言的使用产品和使用场景

当然PowerQuery的M语言应用场景不只是引用在PowerBI和Excel中,它具有广泛的应用场景。目前我们可以在以下产品的使用场景中应用到M语言。 Excel PowerQuery应用Excel通过M语言可以实现整体数据的清洗和重构。  PowerBI 的PowerQuery应用 PowerBI也是通过M语言来实现数据…...

【Linux】遇事不决,可先点灯,LED驱动的进化之路---1

【Linux】遇事不决&#xff0c;可先点灯&#xff0c;LED驱动的进化之路---1 前言&#xff1a; 一、最简单的LED驱动程序 1.1 字符设备驱动程序框架 1.2 程序实战 1.2.1 驱动程序&#xff08;led_drive_simple.c&#xff09; 1.2.2 应用程序&#xff08;led_test_simple.c…...

hive任务reduce步骤卡在99%原因及解决

我们在写sql的时候经常发现读取数据不多&#xff0c;但是代码运行时间异常长的情况&#xff0c;这通常是发生了数据倾斜现象。数据倾斜现象本质上是因为数据中的key分布不均匀&#xff0c;大量的数据集中到了一台或者几台机器上计算&#xff0c;这些数据的计算速度远远低于平均…...

C++11 -- lambda表达式

文章目录 lamaba表达式的引入lambda表达式语法lamabda达式各部分说明捕获列表说明 lamaba表达式底层原理探索 lamaba表达式的引入 在C11之前,如果我们想对自定义类型Goods排序,可以根据姓名,价格,学号按照从大到小或者从小到大的方式排序,可是,这样我们要写额外写6个相关的仿函…...

【开源项目】银行查询服务的设计和实现

银行查询服务的设计和实现 项目地址github&#xff1a;https://github.com/xl-echo/bankInquiryService项目地址gitee&#xff1a;https://gitee.com/xl-echo/bank-inquiry-service 银行查询服务的设计初衷是&#xff1a;为提供更加便利的查询服务&#xff0c;我们在分布式系…...

Linux服务器禁止密码登录,设置秘钥登录

生成SSH密钥 (客户机端) 执行ssh-keygen -t rsa命令创建RSA密钥对&#xff0c;执行结果如下(键入3次回车)&#xff1a; [rootnode01 .ssh]# ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): [回车] En…...

第十八章 开发Productions - ObjectScript Productions - 通过引用或作为输出传递值

文章目录 第十八章 开发Productions - ObjectScript Productions - 通过引用或作为输出传递值典型的回调方法典型的辅助方法 第十八章 开发Productions - ObjectScript Productions - 通过引用或作为输出传递值典型的回调方法典型的辅助方法 第十八章 开发Productions - Object…...