JAVAEE之多线程进阶(2)_ CAS概念、实现原理、ABA问题及解决方案

前言

 在并发编程时,常常会出现线程安全问题,那么如何保证原子性呢?常用的方法就是加锁。在Java语言中可以使用 Synchronized和CAS实现加锁效果。
 Synchronized关键字保证同步的,这会导致有锁,但是锁机制存在以下问题:

  1. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

  2. 一个线程持有锁会导致其它所有需要此锁的线程挂起。

  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。

而volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
 独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap


一、什么是CAS?

1.1 概念:

 CAS全称Compare and swap,字面意思:”比较并交换“,它是一条 CPU 并发原语,用于判断内存中某个值是否为预期值,如果是则更改为新的值,这个过程是原子的。

1.2 实现步骤

具体步骤如下所示:

  1. 一个初始值变量V,值为5;一开始先读取V实际内存中的值赋值给E。
  2. 比如我们需要给最原始的V+1操作,那么此时用E+1来进行操作(这是防止V在其他线程已经被改变),这样完成了U=E+1的操作。
  3. 判断E和V的值是否一致,如果一致则证明在以上操作过程中V没有被其他线程改变则将U的值赋值给V,如果不一致那V就被其他改变了,这样给U的+1操作就不成立,返回当前的V。
    在这里插入图片描述

1.3 CAS 伪代码

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

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

1.4 实现自旋锁

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

二、CAS中的问题

2.1 ABA问题

 ABA 是 CAS 操作的一个经典问题,假设有一个变量初始值为 A,修改为 B,然后又修改为 A,这个变量实际被修改过了,但是 CAS 操作可能无法感知到。
 假设存在两个线程 t1 和 t2,有一个共享变量 num,初始值为 A。接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要

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

但是, 在 t1 执行这两个操作之间,t2 线程可能把 num 的值从 A 改成了 B,又从 B 改成了 A。
在这里插入图片描述

 如果是整形还好,不会影响最终结果,但如果是对象的引用类型包含了多个变量,引用没有变实际上包含的变量已经被修改,这就会造成大问题

2.2 ABA问题带来的BUG

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

我们接下来举一个银行取款大的例子:
假设 小明 有 100 存款,小明想从 ATM 取 50 块钱。取款机创建了两个线程, 并发的来执行 -50 操作。我们期望一个线程执行 -50 成功,另一个线程 -50 失败。如果使用 CAS 的方式来完成这个扣款过程就可能出现问题

正常过程:

  1. 存款 100,线程1 获取到当前存款值为 100,期望更新为 50; 线程2 获取到当前存款值为 100, 期望更新为 50;
  2. 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中;
  3. 轮到线程2 执行了,发现当前存款为 50, 和之前读到的 100 不相同, 执行失败。

异常的过程

  1. 存款 100,线程1 获取到当前存款值为 100,期望更新为 50; 线程2 获取到当前存款值为 100,期望更新为 50;
  2. 线程1 执行扣款成功,存款被改成 50,线程2 阻塞等待中;
  3. 线程2 执行之前,小明的朋友正好给小明转账 50,账户余额变成 100;
  4. 轮到线程2 执行了,发现当前存款为 100,和之前读到的 100 相同,再次执行扣款操作。
    这个时候, 扣款操作被执行了两次! 都是 ABA 问题导致的结果。

2.3 ABA解决方案

给要修改的值,引入版本号,在 CAS 比较数据当前值和旧值的同时,也要比较版本号是否符合预期。

  1. CAS 操作在读取旧值的同时,也要读取版本号。
  2. 真正修改的时候,
    如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 +1;
    如果当前版本号高于读到的版本号,就操作失败(认为数据已经被修改过了)。

我们依然举一个银行取款的例子:

为了解决 ABA 问题, 给余额搭配一个版本号, 初始设为 1:

  1. 存款 100, 线程1 获取到 存款值为 100,版本号为 1,期望更新为 50; 线程2 获取到存款值为 100,版本号为 1,期望更新为 50。
  2. 线程1 执行扣款成功,存款被改成 50,版本号改为2。线程2 阻塞等待中。
  3. 在线程2 执行之前, 小明的朋友正好给小明转账 50, 账户余额变成 100, 版本号变成3.
  4. 轮到线程2 执行了,发现当前存款为 100,和之前读到的 100 相同, 但是当前版本号为 3,之前读 到的版本号为 1,版本小于当前版本, 认为操作失败。

总结

讲解下自己理解的 CAS 机制

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

ABA问题如何解决

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

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

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

相关文章

判断GIF类型并使用ImageDecoder解析GIF图

一、判断是否为GIF图片类型 在JavaScript中,判断用户上传的文件是否为GIF文件类型时,通常可以通过检查文件的type属性或文件的拓展名来判断,但是由于文件拓展名可以轻易被用户修改,type属性是由浏览器根据文件拓展名猜测得出的&a…

douyin-vue:使用Vue3、Pinia和Vite5打造高度还原的抖音仿制项目

一:引言 在前端技术日新月异的今天,Vue.js作为一款流行的前端框架,不断吸引着开发者的目光。最近,GitHub上出现了一个备受瞩目的项目——douyin-vue,这是一个基于Vue3、Pinia和Vite5的移动端短视频项目,旨…

[解决]windows mysql8.0.x误删除root,解决办法

1. 停止mysql服务 2. 以管理员身份打开命令窗口,进入到mysql安装位置的bin目录下 3. 输入 mysqld --console --skip-grant-tables --shared-memory 注意:a. 很多解决办法是输入mysqld --skip-grant-tables,这在mysql8.0之后的版本已经不在…

ASPICE标准:汽车软件与嵌入式系统开发的黄金准则-亚远景

在高度信息化的现代社会,汽车行业的发展已远超过传统的机械和动力系统,汽车电子和软件系统的重要性日益凸显。为了确保汽车软件的质量和可靠性,汽车行业需要一套统一且高效的开发标准。ASPICE(Automotive SPICE)标准应…

通过ESP32芯片模组实现产品智能化升级,启明云端乐鑫代理商

随着科技的不断进步,物联网(IoT)已经渗透到我们生活的方方面面,成为现代生活不可或缺的一部分。在这场智能化革命中,乐鑫科技以其创新的ESP32芯片模组,为智能家居和智能设备的发展注入了新的活力。作为乐鑫…

msi安装mysql8 启动失败,提示只有在任务处于完成状态(RanToCompletion、Faulted 或 Canceled)时才能释放它。

解决方案: 1.打开服务,找到安装的mysql 2. 右击,打开属性,进入【登录】选项卡,选择本地系统账户。 3. 点击确定-->应用 4.服务中选择开始服务 5.服务启动成功后,在安装步骤中继续点击执行

Post Microsoft Build and AI Day 上海开发者日

点击蓝字 关注我们 编辑:Alan Wang 排版:Rani Sun 这个六一怎么过?来微软 Reactor,一起过儿童节吧! 6月1日,Microsoft Azure & Microsoft Reactor 面向大小朋友特别推出六一特辑,「Post Mic…

开源进销存系统

推荐一款开源的进销存系统 项目地址:进销存系统 仓库管理系统 SAAS进销存 进销存ERP: 进销存系统 仓库管理系统 SAAS进销存 进销存ERPhttps://gitee.com/flyemu/jxc.git 主要功能模块 销售 采购 库存 资料 设置 支持saas多租户,100%开源可二开 …

前端项目上线要准备哪些问题?

上线前 一般开发过程中,会准备好几条线路,一个是测试环境,一个是正式环境;如果是公司比较大,在此外还会增加一个环境-预上线。 预上线,就是所有的设备,环境,条件和正式线的环境都是一…

iPhone用户推荐使用的藏汉翻译小助手:藏汉翻译通小程序,支持藏文OCR识别文字提取,卫藏语、安多语、康巴语学习背单词!

网上冲浪时,遇到不会的汉语词汇,可以复制到藏汉翻译通小程序中进行翻译。如果不会拼音,可以使用图片识别功能扫一扫文字,即可OCR识别提取文字。 此外,藏汉翻译通小程序现在还支持背单词和会话速成课程,支持…

C# BurnServiceContractClient 使用方法

目录 1. 添加服务引用 2. 使用客户端进行操作 3. 配置文件(App.config) 4. 异步调用 总结 要在C#中使用BurnServiceContractClient,首先需要了解该客户端的使用场景和目标服务契约。假设BurnServiceContractClient是一个WCF(W…

混元助手 mysql建表语句转换oracle

提示语 mysql建表语句转换成oracle,并且有common SQL语句的生成: create ....

Pytorch-Reduction Ops

文章目录 前言1.torch.argmax()2.torch.argmin()3.torch.amax()4.torch.amin()5.torch.all()6.torch.any()7.torch.max()8.torch.dist()9.torch.logsumexp()10.torch.mean()11.torch.norm()12.torch.nansum()13.torch.prod()14.torch.cumsum()15.torch.cumprod() 前言 1.torch.…

node环境问题(无法加载文件D:\Software\Node.js\node_global\vue.ps1,因为在此系统上禁止运行脚本。)

问题:npm安装lerna显示安装成功,但是lerna -v的时候报错 解决步骤: 1、输入:Get-ExecutionPolicy 2、输入:Set-ExecutionPolicy -Scope CurrentUser(有选项的选Y) 3、输入:RemoteSi…

【记录】打印|无需排版,生成证件照打印PDF,打印在任意尺寸的纸上(简单无损!)

以前我打印证件照的时候,我总是在网上找在线证件照转换或者别的什么。但是我今天突然就琢磨了一下,用 PDF 打印应该也可以直接打印出来,然后就琢磨出来了,这么一条路大家可以参考一下。我觉得比在线转换成一张 a4 纸要方便的多&am…

Python爬虫要掌握哪些东西

学习Python爬虫,你需要掌握以下几个关键方面的知识: 文章目录 Python基础:首先,确保你对Python语言有良好的理解,包括基本语法、数据结构(如列表、字典、集合等)、函数、类和对象、模块和包的使用等。# 有一个数字列表,要创建新的列表,元素是原列表中每个元素的平方 …

深入探索MySQL SELECT查询:从基础到高级,解锁数据宝藏的密钥

系列文章目录 更新ing... MySQL操作全攻略:库、表、数据、事务全面指南深入探索MySQL SELECT查询:从基础到高级,解锁数据宝藏的密钥MySQL SELECT查询实战:练习题精选,提升你的数据库查询技能PyMySQL:连接P…

解决 x-content-sha256 no match 错误,对 S3CrtAsyncHttpClient 修改

一、CRT修改核心逻辑: 找到 software.amazon.awssdk.services.s3.internal.crt 包下 S3CrtAsyncHttpClient 按照逻辑需要对 GET请求进行适配 signingConfig.setSignedBodyValue(AwsSigningConfig.AwsSignedBodyValue.EMPTY_SHA256); if("GET".equals(asyncRequ…

orin部署tensorrt、cuda、cudnn、pytorch、onnx

绝大部分参考https://blog.csdn.net/qq_41336087/article/details/129661850 非orin可以参考https://blog.csdn.net/JineD/article/details/131201121 报错显卡驱动安装535没法安装、原始是和l4t-cuda的部分文件冲突 Options marked [*] produce a lot of output - pipe it t…

数据结构(一)顺序表

目录 一、概念(一)数据结构的三元素1. 逻辑结构(1)线性结构(2)非线性结构 2. 存储结构(1)顺序存储(2)链式存储(3)索引存储 3. 运算 &a…