JavaEE:CAS详解

一.什么是CAS

CAS: 全称 Compare and swap ,字面意思 :” 比较并交换 ,一个 CAS 涉及到以下操作:
 我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
我们来进行操作:
1. 比较 V 和 A 是否相等。(比较)
2. 如果比较是相等的,将B 写入 V。(交换)
3. 返回操作是否成功。
伪代码如下:
boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

这个就大概介绍了CAS的工作流程

二.CAS是如何实现的

针对不同的操作系统,JVM用到了不同的CAS实现原理

  • java CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg
  • Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。

三.CAS的应用

1.实现原子类

我们都知道,在多线程环境下,我们的++操作不是线程安全的。那么我们的CAS就自己将 ++ 操作是现成了原子类。

import java.util.concurrent.atomic.AtomicInteger;public class CAS {public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(0);int ret = atomicInteger.incrementAndGet();System.out.println(ret);}
}

此时在Java中,为我们提供了这样的 ++ 原子类操作。以上代码的运行结果为 1 。

那么我们深究一下这其中的实现原理

先来看下面的伪代码:

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

首先要知道我们的CAS操作时原子的。在多线程环境下,那么他是如何保证每次读到的值,都是我们预期的值呢?

我们看下面的图片:

🎈第一步

两个线程都读取 value 的值到 oldValue . (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)

🎈第二步

线程1进行CAS操作,先进行比较,发现 oldValue 和 Value 都是相同的,然后进行oldValue+1操作,最后再写入到内存中。此时主内存中Value的值为 1

🎈第三步

此时线程2进行操作。再进行 比较 oldValue 和 Value 是否相同的时候,发现不相同。此时重新进入while循环!在循环的时候, 会刷新oldValue的值-->1,此时oldValued 和 Value的值就相同了。正常进行+1赋值操作。

🎈第四步

线程 1 和 线程 2 返回各自的 oldValue 的值即可
我们可以很清楚的看到,CAS使用while循环巧妙的解决了++操作的线程安全问题。

2.实现自旋锁

先来看自旋锁的伪代码:

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

自旋锁其实时一种锁的策略,它的实现原理就是如果当前的线程是有锁的,那么就一直while循环尝试获取锁,如果没锁了,那么就直接可以获取到CPU了。

四.CAS的ABA问题

我们先来看个例子:

假如我要去银行里取钱,我的存款是100元,我期望的是取出50元。

那么取款机创建了两个线程,假如线程1执行了-50的操作,线程2再执行的时候会报错。

1.正常的过程

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

2.异常的过程

1) 存款 100. 线程1 获取到当前存款值为 100, 期望更新为 50; 线程2 获取到当前存款值为 100, 期
望更新为 50.
2) 线程1 执行扣款成功, 存款被改成 50. 线程2 阻塞等待中.
3) 在线程2 执行之前, 我的朋友正好给滑稽转账 50, 账户余额变成 100 !!
4) 轮到线程2 执行了, 发现当前存款为 100, 和之前读到的 100 相同, 再次执行了扣款操作!
那么此时就执行了两次扣款操作,这显然不是我们所期望的。

3.解决办法

给要修改的值 , 引入版本号 . CAS 比较数据当前值和旧值的同时 , 也要比较版本号是否符合预期
版本号随着每次的使用而进行更新
还是刚才银行取钱的案例
为了解决 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, 版本小于当前版本, 认为操作失败.
此时引入了版本号,ABA问题就完美的得到了解决。

总结:CAS是面试中常考的问题,我们需要深度学习,并且理解其中的操作机制。

        

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

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

相关文章

代码随想录算法训练营第二十天| 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树

代码随想录算法训练营第二十天| 654.最大二叉树、617.合并二叉树、700.二叉搜索树中的搜索、98.验证二叉搜索树 题目 654.最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点,其值为 nums 中的最大值…

蓝桥杯第二场小白入门赛(1~5)(对不起,我线段树太菜了)

1.模拟 2.贪心 3.二分 4.数论 5.数论 6.线段树&#xff08;线段树还是练少了...&#xff09; 1. 蓝桥小课堂-平方和 直接模拟&#xff0c;注意数据范围 #include <bits/stdc.h> using namespace std; #define LL long long #define pb push_back #define x first …

低代码开发:数字化转型的引擎

引言 在当今数字化时代&#xff0c;组织面临着不断变化的市场需求和技术挑战。数字化转型已成为维持竞争力的关键&#xff0c;而低代码开发正在崭露头角&#xff0c;成为加速创新和数字化转型的有力工具。本文将深入探讨低代码开发的核心概念、优势和应用&#xff0c;以揭示它…

亚马逊圣诞关键词怎么选?亚马逊圣诞节促销活动有哪些?——站斧浏览器

亚马逊圣诞关键词怎么选&#xff1f; 以下是在亚马逊圣诞期间利用长尾关键词的一些建议&#xff1a; 圣诞主题关键词&#xff1a;随着节日的临近&#xff0c;与圣诞相关的关键词搜索热度将急剧上升。在产品标题、描述、关键词等位置使用与圣诞节相关的关键词&#xff0c;比如…

【数据结构一】初始Java集合框架(前置知识)

Java中的数据结构 Java语言在设计之初有一个非常重要的理念便是&#xff1a;write once&#xff0c;run anywhere&#xff01;所以Java中的数据结构是已经被设计者封装好的了&#xff0c;我们只需要实例化出想使用的对象&#xff0c;便可以操作相应的数据结构了&#xff0c;本篇…

json解析之fastjson和jackson使用对比

前言 最近项目中需要做埋点分析&#xff0c;首先就需要对埋点日志进行解析处理&#xff0c;刚好这时候体验对比了下fastjson和jackson两者使用的区别&#xff0c;以下分别是针对同一个json串处理&#xff0c;最终的效果都是将json数据解析出来&#xff0c;并统一展示。 一、fa…

unity中Android各版本对应的SDK版本

在unity开发中经常出现兼容性的问题&#xff0c;老是忘记Android各版本对应的SDK版本&#xff0c;这里记录一下&#xff0c;以供自己查阅&#xff0c;如果unity打包生成android api过低&#xff0c;那么可能在最新的机型上无法运行&#xff0c;闪退或者各种异常。 平台版本SDK…

python实现批量替换目录下多个后缀为docx文档内容

#!/usr/bin/python # -*- coding: UTF-8 -*- import os from docx import Document from docx.shared import Inchesdef replace_in_word_docs(directory, old_text, new_text):# 遍历指定目录下的所有文件for filename in os.listdir(directory):if filename.endswith(.docx):…

Android将自定义的SurfaceView保存为bitmap

正常将View保存为Bitmap的方法&#xff1a; private Bitmap getViewToBitmap(View view) { // layoutView(view);//创建Bitmap,最后一个参数代表图片的质量.Bitmap bitmap Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);if (bitma…

【CentOS 7.9 分区】挂载硬盘为LVM操作实例

LVM与标准分区有何区别&#xff0c;如何选择 目录 1 小系统使用LVM的益处&#xff1a;2 大系统使用LVM的益处&#xff1a;3 优点&#xff1a;CentOS 7.9 挂载硬盘为LVM操作实例查看硬盘情况格式化硬盘创建PV创建VG创建LV创建文件系统并挂载自动挂载添加&#xff1a;注意用空格间…

pip 常用指令 pip list 命令用法介绍

&#x1f4d1;pip 常用命令归类整理 pip list 是一个用于列出已安装的 Python 包的命令。这个命令会显示出所有已安装的包&#xff0c;以及它们的版本号。 pip list 命令有以下参数 -o, --outdated&#xff1a;列出所有过时的包&#xff0c;即有新版本可用的包。-u, --uptod…

3D 纹理贴图基础知识

在线工具推荐&#xff1a; 3D数字孪生场景编辑器 - GLTF/GLB材质纹理编辑器 - 3D模型在线转换 - Three.js AI自动纹理开发包 - YOLO 虚幻合成数据生成器 - 三维模型预览图生成器 - 3D模型语义搜索引擎 介绍 纹理贴图是创建模型时离不开的最后一块拼图。同样&#xff0c;…

综合评价---DEA数据包络分析

数据包络分析(Data Envelopment Analysis&#xff0c;DEA)&#xff0c;1978年由 Charnes、Cooper和Rhodes创建的一种绩效评价技术(performance technique) 。采用多投入、多产出数据对多个决策单元(Decision Making Unit) 的相对效率进行评价因DEA的诸多优势&#xff0c;被广泛…

JavaScript——数据类型判断方法汇总

1. typeof // 1.typeof 数组、对象和null都会被视为object 其他类型都判定正确 console.log(typeof {}); // object console.log(typeof []); // object console.log(typeof null); // object console.log(typeof function () {}); // function console.log(typeof 1); // num…

【.Net8教程】(一)读取配置文件全面总结

环境&#xff1a;.net8.0 1. 准备条件 先在appsettings.Development.json或appsettings.json添加配置 添加一个DbOption {"DbOption": {"Conn": "foolishsundaycsdn"} }2.直接读取json配置节点的几种写法 在Main函数中读取json配置 方式一 …

Linux环境下通过journal命令查看和管理日志

文章目录 前言问题分析journal 和 syslog 对比journal 和 syslog 配置使用journalctl查看和管理日志查看日志查看指定服务日志查看调整存储 回到文章开头的问题总结 前言 就在半月之前&#xff0c;负责打包更新的服务器突然登录不上去了&#xff0c;赶紧找来运维的同事帮忙解决…

【数据结构之顺序表】

数据结构学习笔记---002 数据结构之顺序表1、介绍线性表1.1、什么是线性表? 2、什么是顺序表?2.1、概念及结构2.2、顺序表的分类 3、顺序表接口的实现3.1、顺序表动态存储结构的Seqlist.h3.1.1、定义顺序表的动态存储结构3.1.2、声明顺序表各个接口的函数 3.2、顺序表动态存储…

b2b订货系统成本是多少

批发贸易企业都上b2b订货系统&#xff0c;b2b订货系统成本究竟是多少&#xff0c;今天我们来算一算&#xff0c;尤其是最后一个大家可能会忽略。 一是b2b订货系统模板的功能费用&#xff0c;这部分一般各大厂家是明码标价&#xff0c;比如易订货12800元/年&#xff0c;核货宝首…

深入理解 Rust 中的容器类型及其应用

Rust 作为一种系统编程语言&#xff0c;提供了丰富的容器类型来处理各种数据结构和算法。这些容器类型不仅支持基本的数据存储和访问&#xff0c;还提供了高效的内存管理和安全性保障。本文将详细介绍 Rust 中的几种主要容器类型&#xff0c;包括它们的用法、特点和适用场景&am…

【IDEA】try-catch自动生成中修改catch的内容

编辑器 --> 文件和代码模板 --> 代码 --> Catch Statement Body