Java多线程篇(5)——cas和atomic原子类

文章目录

  • CAS
  • Atomic 原子类
    • 一般原子类
    • 针对aba问题 —— AtomicStampedReference
    • 针对大量自旋问题 —— LongAdder

CAS

原理大致如下:
在这里插入图片描述
在java的 Unsafe 类里封装了一些 cas 的api。以 compareAndSetInt 为例,来看看其底层实现。
在这里插入图片描述
可以发现,最终会调用到 Atomic::cmpxchg 方法(Atomic::cmpxchg 在不同的操作系统中实现有所不同,上图所示是 linux_x86 的代码)。

Atomic::cmpxchg (exchange_value,dest, compare_value)

compare_value:是期望的当前值,如果 destination 的当前值等于 compare_value,则进行替换操作。
dest: 是要进行比较和替换的内存位置(通常是一个变量或内存地址)。
exchange_value: 是要设置到 destination 的新值。
返回:CAS成功返回替换值,CAS失败返回原值。

Atomic::cmpxchg 底层是 cmpxchgl 指令,该指令是一个硬件层面上的原子操作。除此之外如果是多核架构还加入lock前缀指令以实现内存屏障的效果。

cmpxchgl指令:首先比较 dest 指向的内存值是否和 compare_value 值相等,如果相等,则交换 dest 与 exchange_value,否则就单方面将 dest 指向的值赋给exchange_value。

Atomic 原子类

在理解了cas的底层实现后,再来看原子类。

一般原子类

一般的源自类无非就是直接调用了 Unsafe 中 cas 相关api。

以AtomicInteger为例:
在这里插入图片描述
而上面是在不考虑CAS自身缺陷情况下的一个封装,如果考虑上CAS缺陷,原子类该如何应对?

cas缺陷:1、aba问题 2、可能大量自旋

针对aba问题 —— AtomicStampedReference

试想这么一个场景:从读取内存值开始到执行cas原子指令之前,值被修改了两次,一次+1,一次-1。此时对于cas指令来说,内存中的值是不变的,就好像没被修改过一样,进而指令操作成功。这就是aba问题。
为解决aba问题,jdk增加了 AtomicStampedReference 类。核心设计思想是多加版本号的概念。当cas替换时不仅比对内存中的值是否是期望值,还会去比对版本号是否跟一开始的相等, 如果不相等,所以在这期间被修改过了,此时就cas失败。
在这里插入图片描述
除了 AtomicStampedReference 还有 AtomicMarkableReference 。区别在于 AtomicMarkableReference 并不关心修改了多少次,只关心是否被修改过。所以mark是一个boolean类型。
在这里插入图片描述

针对大量自旋问题 —— LongAdder

试想这么一个场景,假如同一时刻大量线程对库存数量-1,此时必然会有大量的自旋线程占用cpu资源。这明显是一个资源的浪费。如果不用cas,加锁的话,就会有线程切换,对性能必然会有所影响。有没有那么一种方案,既不加锁阻塞线程,又可以避免大量的自旋呢?
为解决这类场景的问题,jdk 1.8 引入了 LongAdder。

除 LongAdder 外还有 DoubleAdder、DoubleAccumulator、LongAccumulator,基本原理是一样的,本文以 LongAdder为例。
LongAccumulator相当于LongAdder的增强版。LongAdder只能针对数值的进行加减运算,而LongAccumulator提供了自定义的函数操作。

LongAdder的基本思路:
分散热点,将value值分散到数组中,不同线程会命中到数组的不同槽,每个线程只对自己槽中的值进行CAS,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只需将各个槽中的变量值累加返回即可。

重要变量:base变量 + Cells[]数组

base变量:非竞态条件下,直接累加到该变量上
Cells[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中

接下来从源码看看 LongAdder 是如何累加的:
LongAdder#add

    public void add(long x) {Cell[] cs; long b, v; int m; Cell c;if ((cs = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (cs == null || (m = cs.length - 1) < 0 ||(c = cs[getProbe() & m]) == null ||!(uncontended = c.cas(v = c.value, v + x)))longAccumulate(x, null, uncontended);}}

在这里插入图片描述
总之就是当 cells 还为初始化就直接cas base变量,如果一开始cells就已经初始化,或者cas base 变量失败就cas cells数组,如果cas cells数组也失败就进入Striped64#longAccumulate。

Striped64#longAccumulate

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {//...//自旋循环done: for (;;) {Cell[] cs; Cell c; int n; long v;//如果cells不为空if ((cs = cells) != null && (n = cs.length) > 0) {//映射的槽位为空,新建并设置槽位if ((c = cs[(n - 1) & h]) == null) {if (cellsBusy == 0) {Cell r = new Cell(x);if (cellsBusy == 0 && casCellsBusy()) {try {Cell[] rs; int m, j;if ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {rs[j] = r;break done;}} finally {cellsBusy = 0;}continue;}}collide = false;}//...(自旋重试的标记位处理)//不为空就 cas cell 槽位else if (c.cas(v = c.value, (fn == null) ? v + x : fn.applyAsLong(v, x)))break;//...(自旋重试的标记位处理)//cas cell 槽位失败尝试扩容cellselse if (cellsBusy == 0 && casCellsBusy()) {try {if (cells == cs)        // Expand table unless stalecells = Arrays.copyOf(cs, n << 1);} finally {cellsBusy = 0;}collide = false;continue;                   // Retry with expanded table}h = advanceProbe(h);}//如果为空就新建 cells 并设置 cell 槽位else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {try {if (cells == cs) {Cell[] rs = new Cell[2];rs[h & 1] = new Cell(x);cells = rs;break done;}} finally {cellsBusy = 0;}}//如果正在扩容,直接cas baseelse if (casBase(v = base,(fn == null) ? v + x : fn.applyAsLong(v, x)))break done;}}

在这里插入图片描述
总之就是如果cells还未初始化就初始化并设置此次槽位。如果正在初始化就直接cas base。如果已经初始化,就映射槽位,如果槽位为null,就创建槽位并设值,如果槽位不为null,就cas该槽位,如果槽位cas失败,就尝试扩容(cells大小 < cpu核数 时扩容)。

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

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

相关文章

GPT,GPT-2,GPT-3,InstructGPT的进化之路

ChatGPT 火遍圈内外&#xff0c;突然之间&#xff0c;好多人开始想要了解 NLP 这个领域&#xff0c;想知道 ChatGPT 到底是个什么&#xff1f;作为在这个行业奋斗5年的从业者&#xff0c;真的很开心让人们知道有一群人在干着这么样的一件事情。这也是我结合各位大佬的文章&…

【AIGC】Stable Diffusion Prompt 每日一练0916

一、前言 1.1 写在前面 本文是一个系列&#xff0c;有点类似随笔&#xff0c;每天一次更新&#xff0c;重点就Stable Diffusion Prompt进行专项训练&#xff0c;本文是第022篇《Stable Diffusion Prompt 每日一练0916》。上一篇《Stable Diffusion Prompt 每日一练0915》 1.…

Android Jetpack Compose之UI的重组和自动刷新

1.概述 我们都知道&#xff0c;在传统的View中&#xff0c;若要改变UI&#xff0c;需要我们修改View的私有属性&#xff0c;比如要修改一个TextView的文字&#xff0c;我们需要通过它的setText(“xxx”)方法去修改。而Compose 则是通过重组来刷新UI。在之前的状态管理的文章中…

基于SSM的田径运动会成绩管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

MySQL详解六:备份与恢复

文章目录 1. 数据库备份的分类1.1 从物理和逻辑上分类1.1.1 物理备份1.1.2 逻辑备份 1.2 从数据库的备份策略角度上分类1.2.1 完全备份1.2.2 差异备份1.2.3 增量备份 1.3 常见的备份方法 2. MySQL完全备份2.1 完全备份简介2.2 优点与缺点2.3 实现物理冷备份与恢复2.3.1 实现流程…

MongoDB(一) windows 和 linux 之 Ubuntu 安装

数据库分类 一、关系型数据库&#xff08;RDBMS&#xff09; mysql 、Oracle、DB2、SQL Server 关系数据库中全都是表 二、非关系型数据库&#xff08;NO SQL&#xff09; MongoDB、Redis 键值对数据库 文档数据库MongoDB 下载 mongoDB https://www.mongodb.com/try/downloa…

渗透中 POC、EXP、Payload、Shellcode 的区别

渗透中 PoC、Exp、Payload、Shellcode 的区别 不同含义&#xff1a; POC Proof of Concept中文意思是“观点证明”。这个短语并非仅仅在漏洞报告中使用&#xff0c;甲方在项目招标过程中也常常要求乙方提供POC&#xff0c;即证明你的方案或者产品能达到声称的功能或性能&…

Tomcat部署、优化、以及操作练习

一.Tomcat的基本介绍 1.1.Tomcat是什么&#xff1f; Tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;T…

详细解释HiveSQL执行计划

一、前言 Hive SQL的执行计划描述SQL实际执行的整体轮廓&#xff0c;通过执行计划能了解SQL程序在转换成相应计算引擎的执行逻辑&#xff0c;掌握了执行逻辑也就能更好地把握程序出现的瓶颈点&#xff0c;从而能够实现更有针对性的优化。此外还能帮助开发者识别看似等价的SQL其…

内存函数 memcpy 和 memmove 的讲解和模拟实现

目录 一.什么是 memcpy 函数 模拟思路 代码实现 二.什么是 memmove 函数 模拟思路 代码实现 一.什么是 memcpy 函数 我们打开 cplusplus 官网查看&#xff1a; memcpy - C Reference (cplusplus.com) 官方文本显示&#xff1a; 内存复制块 将 num 字节的值从源指向的位置…

去耦电路设计应用指南(一)MCU去耦设计介绍

&#xff08;一&#xff09;MCU去耦设计介绍 1. 概述2. MCU需要去耦的原因2.1 去耦电路简介2.2 电源噪声产生的原因2.3 插入损耗2.4 去耦电路简介 参考资料来自网上&#xff1a; 1. 概述 我们经常看到单片机或者IC电路管脚常常会放置一个或者多个陶瓷电容&#xff0c;他们主要…

安防监控视频云存储平台EasyNVR对接EasyNVS时,一直不上线该如何解决?

视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。 近期有用户在使用安防视频平台EasyNVR对接上级平台EasyNVS时&#xff0c;出现了一直不上线…

mysql事务测试

mysql的事务处理主要有两种方法1、用begin,rollback,commit来实现 begin; -- 开始一个事务 rollback; -- 事务回滚 commit; -- 事务提交 2、直接用set来改变mysql的自动提交模式 mysql默认是自动提交的&#xff0c;也就是你提交一个sql&#xff0c;它就直接执行&#xff01;我…

Windows 基于Visual Studio 开发Qt 6 注意事项

前提条件&#xff1a; 1、Visual Studio 2022 社区版(免费版) 2、Qt-6.5.1版本 Qt Vistual Studio Tools下载 先打开Visual Studio 2022 社区版 &#xff1a; 点击扩展-》管理拓展按钮后&#xff0c;在搜索框中输入Qt&#xff0c;点击这里第一个扩展安装。 Qt Visual Stud…

计算机毕设 图像识别-人脸识别与疲劳检测 - python opencv

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业…

云计算战略:选择适合你业务的云平台

文章目录 云计算的概述选择云平台的关键因素1. 业务需求2. 预算3. 性能要求4. 数据隐私和合规性 示例&#xff1a;选择适合的云平台业务需求预算性能要求数据隐私和合规性 代码示例&#xff1a;使用云平台服务结论 &#x1f389;欢迎来到云计算技术应用专栏~云计算战略&#xf…

Linux下ThinkPHP5实现定时器任务 - 结合crontab

实例一&#xff1a; 1.在/application/command创建要配置的PHP类文件&#xff0c;需要继承Command类&#xff0c;并重写configure和execute两个方法&#xff0c;例如: <?php namespace app\command; use think\console\Command; use think\console\Input; use think\cons…

uvm源码解读-sequence,sequencer,driver三者之间的握手关系1

1.start item 1.start_item();sequencer.wait_for_grant(prior);this.pre_do(1);需要指出&#xff0c;这里明确说明了wait_for_grant和send_request之间不能有任何延迟&#xff0c;所以在mid_do这个任务里千万不能有任何延迟。 task uvm_sequencer_base::wait_for_grant(uvm…

OpenCV实现图像边缘检测(sobel算子、laplacian算子、Canny边缘检测)

边缘检测原理 1&#xff0c;Sobel算子 1.1代码实现 import cv2 as cv import numpy as np import matplotlib.pyplot as plt from pylab import mplmpl.rcParams[font.sans-serif] [SimHei]img cv.imread("lena.png",0)#计算sobel卷积结果 x cv.Sobel(img,cv.CV_…

MySQL数据库详解 二:数据库的高级语言和操作

文章目录 1. 克隆表 ---- 将数据表的数据记录生成到新的表中1.1 方式一&#xff1a;先创建新表&#xff0c;再导入数据1.2方式二&#xff1a;创建的时候同时导入 2. 清空表 ---- 删除表内的所有数据2.1 delete删除2.2 truncate删除&#xff08;重新记录&#xff09;2.3 创建临时…