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

《Java-SE-第二十八章》之CAS

前言

在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!”

博客主页:KC老衲爱尼姑的博客主页

博主的github,平常所写代码皆在于此

共勉:talk is cheap, show me the code

作者是爪哇岛的新手,水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


文章目录

  • CAS
    • 什么是CAS?
    • CAS是怎么实现的
      • CAS的应用
        • 1. 原子类
        • 2. 实现自旋锁
      • CAS的ABA问题
        • 什么是ABA问题
        • ABA问题引来的BUG
          • ABA问题复现
          • 解决方案

CAS

什么是CAS?

  CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:把内存中的某个值和CPU寄存器A中的值,进行比较,如果两个值相同,就把另一个寄存器B中的值个内存的值进行交换,也就是把内存的值放到寄存器B,同时把寄存器B的值写给内存。

CAS 伪代码如下:

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. 原子类

  Java标准库中提供了 java.util.concurrent.atomic 包, 里面的类都是基于这种方式来实现的,这些类名都以Atomic开头,针对基础的数据类型进行封装,由于是基于CAS实现的,所以都是线程安全的。

在这里插入图片描述

以 AtomicInteger 举例,常见方法有

addAndGet(int delta); i += delta;

decrementAndGet(); --i;

getAndDecrement(); i–;

incrementAndGet(); ++i;

getAndIncrement(); i++;

使用演示

两个线程对同一个变量,各自自增5000,使其达到一万,这次不加锁,使用AtomicInteger 来实现

代码如下:


import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo {private static AtomicInteger counter = new AtomicInteger();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i <5000;i++) {counter.getAndIncrement();}});Thread t2 = new Thread(() -> {for (int i = 0; i <5000;i++) {counter.getAndIncrement();}});t1.start();t2.start();t1.join();t2.join();System.out.println(counter);}
}

运行结果:
在这里插入图片描述

getAndIncrement ()的伪代码实现

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

CAS中的i++

假设两个线程同时调用 getAndIncrement

(1) 两个线程都读取 value 的值到 oldValue 中

在这里插入图片描述

(2)线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值(value=value+1)

在这里插入图片描述

(3)线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值. 因此需要进入循环. 在循环里重新读取 value 的值赋给 oldValue

在这里插入图片描述

(4)线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作.

在这里插入图片描述

  1. (线程1 和 线程2 返回各自的 oldValue 的值即可.

2. 实现自旋锁

此外基于CSA还可以实现自旋锁

伪代码如下:

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

  上述代码逻辑,如果当前锁对象被线程占用,则lock()方法会 不断的获取锁是否释放,一旦释放了就将owner置为null,然后根据CAS操作将占用该锁的线程设置为当前的线程,并日退出lock()方法,如果是要解锁,就将占用锁对象的线程设置为null。

CAS的ABA问题

什么是ABA问题

  通过上述介绍了CAS的操作,该操作最主要的就是先比较,满足条件后交换。但是这存在一个非常极端的情况,假设有2个线程t1和t2,有一个共享变量num,初始值为A,接下里,线程t1想使用CAS把num值修改为Z,由于需要进行CAS操作,就需要先读取num的值,保存到oldNum中m,然后CAS判断当前的num的值是否为A,如果是A,就修改成Z。但是t1执行这两个操作之间,t2线程可能把num的值从A修改成B,又从B修改成A。而线程t1中的CAS的期望啥num的值不变就修改,但是num被t2线程修改了,只不过又改回来了,此时t1是无法判断当前的这个变量始终是A,还是经历了一个变化的过程,那么是否要更新num的值为Z呢。这就是ABA问题,举个栗子,来记忆一下,张三和李四是一对情侣,某一天闹掰了,就分手了,张三在分手期间又找了个女朋友,过了半年又和新的女朋友分手了和李四又在一起了,这个过程中,李四是不知道张三已经移情别恋了。

ABA问题引来的BUG

  假设张三有100存款,张三想去ATM取50块钱,取款机创建了2个线程来并发执行这个操作。我们期望一个线程执行成功,另一个线程执行失败,如果使用CAS的方式来来完成这个扣款的过程就会出bug。

正常的过程

  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 相同, 再次执行扣款操作

此时ATM就会扣款2次,预期结果是扣一次50,结果扣了2次。

ABA问题复现
public class AtomicReferenceDemo {public static AtomicReference<String> ref = new AtomicReference<String>("A");public static void main(String[] args) throws InterruptedException {System.out.println("main start ...");String prev = ref.get();update();Thread.sleep(1000);System.out.println("change A->Z "+ref.compareAndSet(prev, "Z"));}private static void update() throws InterruptedException {new Thread(() -> {System.out.println("change A-B");ref.compareAndSet(ref.get(), "B");},"t1").start();new Thread(() -> {System.out.println("change B-A");ref.compareAndSet(ref.get(), "A");},"t2").start();}
}

运行结果:
在这里插入图片描述

解决方案

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

  在 Java 标准库中提供了 AtomicStampedReference 类. 这个类可以对某个类进行包装, 在内部就提供了上面描述的版本管理功能.

AtomicStampedReference使用演示

代码如下

import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicStampedReferencedDemo {static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);public static void main(String[] args) throws InterruptedException {System.out.println("main start ...");//获取A的值String prev = ref.getReference();//获取版本号int stamp = ref.getStamp();System.out.println(stamp);update();Thread.sleep(1000);System.out.println("change A->Z "+ref.compareAndSet(prev, "Z",stamp,stamp+1));}private static void update() throws InterruptedException {new Thread(() -> {System.out.println("change A-B");ref.compareAndSet(ref.getReference(), "B",ref.getStamp(), ref.getStamp()+1);System.out.println("t1的版本号:"+ref.getStamp());},"t1").start();new Thread(() -> {System.out.println("change B-A");ref.compareAndSet(ref.getReference(), "A",ref.getStamp(), ref.getStamp()+1);System.out.println("t2的版本号:"+ref.getStamp());},"t2").start();}}

运行结果:
在这里插入图片描述


 各位看官如果觉得文章写得不错,点赞评论关注走一波!谢谢啦!。

在这里插入图片描述

相关文章:

《Java-SE-第二十八章》之CAS

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…...

git之reflog分析

写在前面 本文一起看下reflog命令。 1&#xff1a;场景描述 在开发的过程中&#xff0c;因为修改错误&#xff0c;想要通过git reset命令恢复到之前的某个版本&#xff0c;但是选择提交ID错误&#xff0c;导致多恢复了一个版本&#xff0c;假定&#xff0c;该版本对应的内容…...

《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(18)-Fiddler如何接口测试,妈妈再也不担心我不会接口测试了

1.简介 Fiddler最大的优势在于抓包&#xff0c;我们大部分使用的功能也在抓包的功能上&#xff0c;fiddler做接口测试也是非常方便的。 领导或者开发给你安排接口测试的工作任务&#xff0c;但是没有给你接口文档&#xff08;由于开发周期没有时间出接口文档&#xff09;&…...

Oracle open JDK和 Amazon Corretto JDK的区别

Oracle OpenJDK和Amazon Corretto JDK都是基于Java开放源代码项目的发行版&#xff0c;它们之间有一些区别。 1. 来源&#xff1a;Oracle OpenJDK是由Oracle公司领导和支持的&#xff0c;它是Java的官方参考实现之一。而Amazon Corretto JDK是由亚马逊公司开发和支持的&#xf…...

Spark写PGSQL分区表

这里写目录标题 需求碰到的问题格式问题分区问题&#xff08;重点&#xff09; 解决完整代码效果 需求 spark程序计算后的数据需要往PGSQL中的分区表进行写入。 碰到的问题 格式问题 使用了字符串格式&#xff0c;导致插入报错。 val frame df.withColumn("insert_t…...

Git 命令行登录

有时候登录命令行版本的git会出现这个错误 1remote: Support for password authentication was removed on August 13, 2021. 2remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for …...

性能分析记录

4实例压测TPS浮动在200-300 1.TPS浮动200-300&#xff0c;ART浮动的可能性是10-20ms&#xff0c;链路复杂是可接受的&#xff0c;链路简单则需要分析原因。 1&#xff09;缓存没命中&#xff0c;对某些账号缓存没命中&#xff0c;或缓存失效后导致隔段时间耗时升高。 2&…...

Java反射学习(大综合)

第一天 Java反射及动态代理... 2 一、 Java反射... 2 1、什么是反射&#xff1a;... 2 2、反射的原理... 2 3、反射的优缺点&#xff1a;... 2 4、反射的用途&#xff1a;... 3 5、反射机制常用的类&#xff1a;... 3 1、获得Class&#xff1a;主要有三…...

Vite+Vue3 开发UI组件库并发布到npm

一直对开源UI组件库比较感兴趣&#xff0c;摸索着开发了一套&#xff0c;虽然还只是开始&#xff0c;但是从搭建到发布这套流程基本弄明白了&#xff0c;现在分享给大家&#xff0c;希望对同样感兴趣的同学有所帮助。 目前我的这套名为hasaki-ui的组件库仅有两个组件&#xff0…...

vue- form动态表单验证规则-表单验证

前言 以element官网的form表单的-动态增减表单项为例讲解表单验证规则 动态的功能就是v-model配合push v-for 便利来实现的 我们需要熟知2个知识点prop表单验证需要跟v-model绑定的值是一样的&#xff0c; 如果是一个数组便利的表单&#xff0c;那就需要绑定这个数组每一项…...

FPGA学习—通过数码管实现电子秒表模拟

文章目录 一、数码管简介二、项目分析三、项目源码及分析四、实现效果五、总结 一、数码管简介 请参阅博主以前写过的一篇电子时钟模拟&#xff0c;在此不再赘述。 https://blog.csdn.net/qq_54347584/article/details/130402287 二、项目分析 项目说明&#xff1a;本次项目…...

区块链媒体发稿:区块链媒体宣发常见问题解析

据统计&#xff0c;由于区块链应用和虚拟货币的兴起&#xff0c;越来越多媒体对区块链领域开展报导&#xff0c;特别是世界各国媒体宣发全是热火朝天。但是&#xff0c;随着推卸责任媒体宣发的五花八门&#xff0c;让很多人因而上当受骗&#xff0c;乃至伤害一大笔资产。身为投…...

openGauss学习笔记-28 openGauss 高级数据管理-NULL值

文章目录 openGauss学习笔记-28 openGauss 高级数据管理-NULL值28.1 IS NOT NULL28.2 IS NULL openGauss学习笔记-28 openGauss 高级数据管理-NULL值 NULL值代表未知数据。无法比较NULL和0&#xff0c;因为它们是不等价的。 创建表时&#xff0c;可以指定列可以存放或者不能存…...

DAO和XML文件参数和返回值

①MyBatis中resultType和resultMap的区别 1.使用MyBatis查询数据库记录时&#xff0c;返回类型常用的有两种&#xff1a;resultType和resultMap。那么两者之间有什么区别呢&#xff1f; 如果只是返回一个值&#xff0c;简单类型&#xff0c;比如说String或者int&#xff0c;那…...

vue 浏览器右侧可拖拽小组件

目录 0. 使用场景 1. 动图示例 2. 实现方式 2.1 创建drag.js 2.2 使用v-drag 3. 结尾 0. 使用场景 很多网页在浏览器右侧有"导航"或者“智能助手”的悬浮小气泡框&#xff0c;比如我们的csdn☞ 作为页面友好型的引导标注&#xff0c;某些场景下这些小气泡可以…...

SpringMvc学习笔记五

Restful 风格路由 1. 配置类 1.1、SpringMvcConfig配置类 Configuration ComponentScan({"com.itheima.controller", "com.itheima.config"}) 方式1.2 添加com.itheima.config 扫描目录 EnableWebMvc public class SpringMvcConfig { } 1.2、ServletCo…...

ORACLE-DG总结

述 当主库的某些日志没有成功传送到备库,那么这时候就发生了归档裂缝(Archive Gap)。目前Oracle提供了两种日志GAP的检测和处理机制,分别是自动GAP处理(Automatic Gap Resolution)和FAL进程GAP处理(FAL Gap Resolution)。自动GAP处理即主库上的ARCn进程会每分钟检查备库…...

机器学习中的 K-均值聚类算法及其优缺点

K-均值聚类算法是一种常用的无监督学习算法&#xff0c;用于将相似的数据点分组为聚类。 其步骤如下&#xff1a; 1. 初始化&#xff1a;选择聚类数K&#xff0c;随机选取K个聚类中心。 2. 计算距离&#xff1a;计算每个数据点与K个聚类中心的距离&#xff0c;将其分配到距离最…...

【数据化分析和建模】一般步骤(个人工作经验总结)

近期关于【数据化分析和建模】一般步骤的思考如下。 以终为始&#xff0c;要解决什么问题&#xff0c;实现什么效果&#xff1f; 数据可视化分析的首要目标是通过将数据以可视化图表的形式真实、完整地呈现业务现状&#xff0c;为发现业务问题打好基础&#xff0c;包括实时的…...

视频安防监控EasyCVR平台海康大华设备国标GB28181告警布防的报文说明

TSINGSEE青犀视频监控综合管理平台EasyCVR基于云边端协同&#xff0c;可支持海量视频的轻量化接入与汇聚管理。平台既具备传统安防视频监控的能力&#xff0c;比如&#xff1a;视频监控直播、云端录像、云存储、录像检索与回看、告警上报、平台级联、云台控制、语音对讲等&…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

【SpringBoot自动化部署】

SpringBoot自动化部署方法 使用Jenkins进行持续集成与部署 Jenkins是最常用的自动化部署工具之一&#xff0c;能够实现代码拉取、构建、测试和部署的全流程自动化。 配置Jenkins任务时&#xff0c;需要添加Git仓库地址和凭证&#xff0c;设置构建触发器&#xff08;如GitHub…...

高考志愿填报管理系统---开发介绍

高考志愿填报管理系统是一款专为教育机构、学校和教师设计的学生信息管理和志愿填报辅助平台。系统基于Django框架开发&#xff0c;采用现代化的Web技术&#xff0c;为教育工作者提供高效、安全、便捷的学生管理解决方案。 ## &#x1f4cb; 系统概述 ### &#x1f3af; 系统定…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...

STL 2迭代器

文章目录 1.迭代器2.输入迭代器3.输出迭代器1.插入迭代器 4.前向迭代器5.双向迭代器6.随机访问迭代器7.不同容器返回的迭代器类型1.输入 / 输出迭代器2.前向迭代器3.双向迭代器4.随机访问迭代器5.特殊迭代器适配器6.为什么 unordered_set 只提供前向迭代器&#xff1f; 1.迭代器…...

第21节 Node.js 多进程

Node.js本身是以单线程的模式运行的&#xff0c;但它使用的是事件驱动来处理并发&#xff0c;这样有助于我们在多核 cpu 的系统上创建多个子进程&#xff0c;从而提高性能。 每个子进程总是带有三个流对象&#xff1a;child.stdin, child.stdout和child.stderr。他们可能会共享…...

SpringSecurity+vue通用权限系统

SpringSecurityvue通用权限系统 采用主流的技术栈实现&#xff0c;Mysql数据库&#xff0c;SpringBoot2Mybatis Plus后端&#xff0c;redis缓存&#xff0c;安全框架 SpringSecurity &#xff0c;Vue3.2Element Plus实现后台管理。基于JWT技术实现前后端分离。项目开发同时采 …...

AIGC 基础篇 Python基础 02

1.bool类型 书接上回&#xff0c;我们上次最后讲了三大数据类型&#xff0c;除了这三个之外&#xff0c;Python也有bool类型&#xff0c;也就是True和False。 a 2 print(a1) print(a2) 像这里&#xff0c;输出的内容第一个是False&#xff0c;因为a的值为2&#xff0c;而第…...

jieba实现和用RNN实现中文分词的区别

Jieba 分词和基于 RNN 的分词在技术路线、实现机制、性能特点上有显著差异&#xff0c;以下是核心对比&#xff1a; 1. 技术路线对比 维度Jieba 分词RNN 神经网络分词范式传统 NLP&#xff08;规则 统计&#xff09;深度学习&#xff08;端到端学习&#xff09;核心依赖词典…...