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

news2024/10/6 22:28:06

前言

 在并发编程时,常常会出现线程安全问题,那么如何保证原子性呢?常用的方法就是加锁。在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.coloradmin.cn/o/1717033.html

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

相关文章

当你想使用预测概率作为预测模型的结果

🌟当你想使用预测概率作为医学预测模型的结果🌟 分类预测模型的预测结果有两种。一种是直接判断是哪一类,这种情况下唯一可以调节的就是决策阈值,或者是默认的0.5,也可以是参数确定的决策阈值,比如约登指数…

ELK 使用 metricbeat监控数据

IP功能版本192.168.140.153elk-18.13.4192.168.140.153metricbeat8.13.4192.168.140.156elk-28.13.4192.168.140.156metricbeat8.13.4192.168.140.159logstash8.13.4192.168.140.159kibana8.13.4 一、安装ELK 参考文档: https://download.csdn.net/download/weix…

Spring 源码:深度解析AOP源码配置解析

文章目录 一、 解析AOP配置的入口1.1 从XML配置到AOP Namespace的解析流程1.2 分析注解驱动的AOP配置解析流程 二、AOP配置解析的核心流程2.1 ConfigBeanDefinitionParser 类2.2 parse()2.3 parseAdvisor()2.4 parseAspect()2.5 parsePointcut()2.6 createAdvisorBeanDefinitio…

CentOS 7基础操作02_优化Linux操作系统中的服务

1、实验环境 公司在文件服务器中新安装了CentOS系统.由于默认启动的服务程序较多,系统运行缓慢。现需要对系绞服务进行适当优化,减少一些不必要的自启动服务.并设置系统在开机后直接进入字符模式。 2、需求描述 根据实际使用需求对CentOS 7操作系统中的…

C++第二十二弹---vector深度剖析及模拟实现(下)

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、容量操作 2、内容修改操作 3、打印函数 4、迭代器失效 4.1、什么是迭代器失效 4.2、哪些操作会引起迭代器失效 总结 1、容量操作 size()…

【Docker】宝塔创建Docker容器配置nginx

前言 本篇是我入门docker的第一篇,由于docker具有很好的移植性,易于安装,开箱即用;签约的公司项目开发需要我进行学习,否则money减半,5555~ 百度找了一圈,只有关于docker怎么装宝塔服务器的却没…

从Socket到WebSocket

前言 不知道大家在学习网络编程的时候都是怎样的一种方式,我谨以此文章来记录我自己从头开始学习C网络编程时的经历,中间有许多我自己的一些想法和思考。当然作为一个刚开始学习的新手来说,有些内容也许不那么正确,只是代表了我在…

【Qt知识】Qt Creator快捷键

以下是Qt Creator中的一些常用快捷键列表(持续更新): 基本编辑 多行注释/取消多行注释: Ctrl /编译工程: Ctrl B运行工程: Ctrl R整行上移/下移: Ctrl Shift ↑/↓查找: Ctrl F函数声明和定义切换: F2向下查找: F3头文件和源文件切换:…

SAPUI5基础知识3 - 引导过程(Bootstrap)

1. 背景 在上一篇博客中,我们已经建立出了第一个SAPUI5项目,接下来,我们将为这个项目添加引导过程。 在动手练习之前,让我们先解释一下什么引导过程。 1.1 什么是引导过程? 在计算机科学中,引导过程也称…

苹果如何设置自动循环壁纸?这几种不同的方法你要学会

在使用手机的时候,我们可能会经常更换手机壁纸,遇见好看或者有意义的图片时就想将其设置为壁纸,你知道 iPhone 手机可以设置自动循环壁纸吗?如果你不了解下面就带你一起看看 iPhone 手机如何设置壁纸自动循环。 壁纸程序设置 想…

GIS之arcgis系列06:线划图缓冲区分析

缓冲区工具将在输入要素周围指定距离内创建缓冲区面。 缓冲区例程将遍历输入要素的每个折点并创建缓冲区偏移。 通过这些偏移创建输出缓冲区要素 原理: 01.打开文件 02.确定单位,在文件属性里。 03.工具箱-->分析工具-->邻域分析-->缓冲区。 …

藏汉翻译通小程序——你口袋里的藏语翻译助手!支持高精度藏文OCR文字识别提取,安卓iPhone手机都能用的藏汉翻译小助手!

想要快速学习藏语或者与藏族朋友无障碍沟通吗?藏汉翻译通小程序绝对是你的不二之选! 这款小程序不仅界面简洁、操作便捷,更重要的是它集合了多种实用功能于一身。 双语翻译:无论你是藏族还是汉族,只需输入文字&#…

C#中的实体属性详解与示例

文章目录 实体属性的定义实体属性的访问实体属性的示例总结 在C#中,实体属性是面向对象编程的重要组成部分。实体属性允许我们定义对象的特征和行为,并提供了一种方式来访问和管理这些特征。通过实体属性,我们可以封装对象的状态,…

【Linux】Linux基本指令3

目录 1.date指令 2.cal指令 3.find指令:(灰常重要) -name 4.grep指令——行文本过滤工具 5.zip/unzip指令: 6.tar指令(重要):打包/解包,不打开它,直接看内容 7.bc…

OSPF学习笔记(状态机)

1、邻居关系 OSPF设备启动后,会通过OSPF接口向外发送Hello报文,收到Hello报文的OSPF设备会检查报文中所定义的参数,如果双方一致就会形成邻居关系,两端设备互为邻居 2、邻接关系 形成邻居关系后,如果两端设备成功交…

Spring boot集成通义千问大模型

Spring boot集成通义千问大模型 背景 我在用idea进行java开发时发现了通义灵码这款免费的智能代码补全插件,用了一段时间了,感觉很不错。就想着在自己的项目中也能集成通义千问大模型实现智能回答,毕竟对接openai需要解决网络问题&#xff…

[学习笔记]知乎文章-PyTorch的Transformer

参考资料: PyTorch的Transformer register_buffer的作用是:登记成员变量,它会自动成为模型中的参数,随着模型移动(gpu/cpu)而移动,但是不会随着梯度进行更新。 参考资料:【Torch API…

【云原生】kubernetes中pod的生命周期、探测钩子的实战应用案例解析

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

向量数据库引领 AI 创新——Zilliz 亮相 2024 亚马逊云科技中国峰会

2024年5月29日,亚马逊云科技中国峰会在上海召开,此次峰会聚集了来自全球各地的科技领袖、行业专家和创新企业,探讨云计算、大数据、人工智能等前沿技术的发展趋势和应用场景。作为领先的向量数据库技术公司,Zilliz 在本次峰会上展…

Pont在小程序开发的使用

Pont是一个很好的前后端桥,但是有个问题。默认产生的代码,无法支持微信小程序开发。根本原因是因为使用了window给全局的对象注入了API和refs属性,由于小程序没有window属性,当然就无法使用了,解决办法也比较简单。只需…