【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(比较与交换),它是一种多线程同步的技术,常用于实现无锁算法,从而提高多线程程序的性能和扩展性。本篇文章具体讲解如何使用 CAS 的机制以及 CAS 机制带来的问题。 目录 1. 什么是CAS&…...
flex布局总结
flex布局总结 总结自:https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 内容: flex意思是-弹性布局,可以为盒型模型提供极大的灵活性,设置为flex布局后,子元素的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 (韩老师课程)第三章
变量的介绍 * 变量是程序的基本组成单位 * 变量相当于内存中一个数据存储空间的表示 * 变量在该区域有自己的名称和类型 * 变量必须先声明,后使用,即顺序 * 变量在该区域的数据/值可以在同一类型内不断变化 * 变量在同一个作用域中不能重…...

【P38】JMeter 随机控制器(Random Controller)
文章目录 一、随机控制器(Random Controller)参数说明二、测试计划设计2.1、测试计划一2.2、测试计划二2.3、勾选忽略子控制器块 一、随机控制器(Random Controller)参数说明 可以让控制器内部的逻辑随机执行一个,一般…...

API电商 ERP 数据管理
没有 API,应用之间的通信将会被扼杀;软件开发者将不断重写并执行相同功能的软件;创新的脚步将会放缓。 API 随处可见。大到一个软件系统,小到几行程序,只要具备了一定的特征,都可以被称作 API。那么&#…...
【SQLAlchemy】第四篇——事务
可以把事务理解为一系列操作的集合:这些操作要么全部执行,要么一个也不执行——这样就可以保证数据的一致性和可靠性。在执行更新和删除操作时,尤其要注意利用事务的这个特征。 SQLAlchemy中提供了许多方法来利用事务。 1、如何确保操作生效…...
浅谈QMap中erase与remove的区别
QMap中erase与remove的区别 QMap中erase与remove的区别分别使用erase和remove删除元素使用erase删除元素使用remove删除元素代码讲解 QMap中erase与remove的区别 在实践中发现erase删除元素之后,其迭代器自动指向下一个元素,而remove删除元素之后迭代器…...

FastThreadLocal 原理解析
FastThreadLocal 每个 FastThread 包含一个 FastThreadLocalMap,每个 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数据的过程中,我们常常需要删除某些不需要的列。那么,如何高效地按位置删除Pandas DataFrame的多重索引列呢? 今天分享在Pandas中按位置删除多重索引列的具体方法: 第一步:获取所有列标签 使用df.columns获取DataFrame的所有列标…...
第五十七天学习记录:C语言进阶:结构体链表的自学
先展示一段代码: #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代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

ICV报告: 智能座舱SoC全球市场规模预计2025年突破50亿美元
在智能化、互联化车辆需求不断增加的推动下,汽车行业正在经历一场范式转变。这一转变的前沿之一是智能座舱SoC。本市场研究报告对智能座舱SoC市场进行了全面的分析,包括其应用领域、当前状况和主要行业参与者。 智能座舱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.编写测试程序 前言 最近完成了一个项目&…...
工业传感器有哪些?
工业传感器是指能在工业制造过程能将感受的力、热、光、磁、声、湿、电、环境等被测量转换成电信号输出的器件与装置,在各种化工、机械、汽车等工业场景上都有应用。 工业传感器有哪些? 工业传感器由于不同的特性也被分为多种不同的类别,主要…...
Docker应用部署之Nginx
部署nginx 要求:在docker容器中部署nginx,并通过外部机器访问nginx 步骤: 1.搜索nginx镜像 docker search nginx 2.拉取nginx镜像 docker pull nginx 3.创建容器 #在root目录下创建nginx目录用于存放nginx项目 mkdir ~/nginx cd ~/ng…...

TerminalWorks TSPrint/TSScan/TSWebCam Crack
/ 远程桌面打印软件,TerminalWorks TSPrint Server/Client 从远程服务器打印到本地打印机的 简单方法 TSPrint 为您提供了一个简单的远程桌面打印软件,以及使 Windows 终端服务操作更容易的附加工具。有选择地启用或禁用功能,以便您可以完全…...
如何使用Springboot实现文件上传和下载,并为其添加实时进度条的功能
文件上传和下载是Web开发中非常基础的功能,但在实际开发中,我们经常需要实时显示文件上传或下载的进度。这篇文章将介绍如何使用Springboot实现文件上传和下载,并为其添加实时进度条的功能。 文件上传 添加Maven依赖项 首先,我…...

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

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

用机器学习破解新能源领域的“弃风”难题
音乐发烧友深有体会,玩音乐的本质就是玩电网。火电声音偏暖,水电偏冷,风电偏空旷。至于太阳能发的电,则略显朦胧和单薄。 不知你是否有感觉,近两年家里的音响声音越来越冷,听起来越来越单薄? —…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...