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

news2024/10/6 18:25:22

文章目录

  • 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 槽位失败尝试扩容cells
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == cs)        // Expand table unless stale
                            cells = 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 base
            else 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.coloradmin.cn/o/1034596.html

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

相关文章

基于abaqus的非等速生长Voronoi晶体模型生成插件

1. 非等速生长晶体模型简介 对于标准Voronoi而言&#xff0c;每个晶粒的生长速率是相同的&#xff0c;任意两个晶粒的交界线为其形核点连线的垂直平分线&#xff0c;交界线为一条直线&#xff0c;如图1.1所示。 图1.1 标准Voronoi晶粒交界线 而对于非等速生长Voronoi晶体而言…

项目篇——java文档搜索引擎

Java 文档搜索引擎 文章目录 Java 文档搜索引擎一、分词二、完成parser 类2.1、排除非html文件2.2、解析html以下是解析 HTML 标题的方法以下是解析 对应的 URL以下是解析 HTML的正文&#xff1a; 补充&#xff1a;倒序索引 三、实现 index 类3.1、实现索引结构3.2、索引中新增…

学会使用Git 和 GitHub

Git 和 GitHub 都是程序员每天都要用到的东西 —— 前者是目前最先进的 版本控制工具&#xff0c;拥有最多的用户&#xff0c;且管理着地球上最庞大的代码仓库&#xff1b;而后者是全球最大 同性交友 代码托管平台、开源社区。 在没有这两个工具时&#xff0c;编程可能是这样的…

Go环境搭建

下载 官网地址 选择Download 根据自己的请选择对应的版本进行下载(我这里使用windos go.1.21.1版本) 我这里选择zip类型进行下载免安装版(你也可以选择mis类型进行下载,安装版一步一步next就行)。 安装 解压安装包 目录说明 配置环境变量 验证是否安装成功 完成上述配置…

通过插件去除Kotlin混淆去除 @Metadata标记

在Kotlin中&#xff0c;Metadata是指描述Kotlin类的元数据。它包含了关于类的属性、函数、注解和其他信息的描述。Metadata的作用主要有以下几个方面&#xff1a; 反射&#xff1a;Metadata可以用于在运行时获取类的信息&#xff0c;包括类的名称、属性、函数等。通过反射&…

【功能设计】数据分发功能设计

文章目录 设计脑图功能性非功能性 功能设计文档1. 需求分析1.1、功能性需求1.2、非功能性需求 2. 功能设计2.1 业务流程图2.2 数据流图2.3 表结构设计2.4 接口设计2.5 功能点 3.非功能性设计3.1 性能3.2 可用性3.3 并发性3.4 安全性 设计脑图 功能性 非功能性 功能设计文档 1…

国庆中秋特辑(三)使用生成对抗网络(GAN)生成具有节日氛围的画作,深度学习框架 TensorFlow 和 Keras 来实现

要用人工智能技术来庆祝国庆中秋&#xff0c;我们可以使用生成对抗网络&#xff08;GAN&#xff09;生成具有节日氛围的画作。这里将使用深度学习框架 TensorFlow 和 Keras 来实现。 一、生成对抗网络&#xff08;GAN&#xff09; 生成对抗网络&#xff08;GANs&#xff0c;…

FFMpeg zoompan 镜头聚焦和移动走位

案例 原始图片 # 输出帧数&#xff0c;默认25帧/秒&#xff0c;25*4 代表4秒 # s1280x80 # 输出视频比例&#xff0c;可以设置和输入图片大小一致 # zoom0.002 表示每帧放大的倍数&#xff0c;下面代码是25帧/每秒 * 4秒&#xff0c;共1000帧 # 最终是 0.002*25*4 0.2&…

什么是深度学习?最易懂的机器学习入门文章

1. 什么是深度学习? 深度学习是机器学习领域中一个新的研究方向&#xff0c;它被引入机器学习使其更接近于人工智能。 原文&#xff1a;【科普&实践】超详细&#xff01;一文带你玩转深度学习 - 飞桨AI Studio星河社区 深度学习是机器学习领域中一个新的研究方向&#xff…

折腾LINUX复古终端

这个复古终端是cool-retro-term&#xff0c;先来图 点击GITHUB地址 开始用docker运行&#xff0c;报错。后来用x11docker&#xff0c;因为我要远程通过SSH的x11转发&#xff0c;但实际x11docker的默认backbone就说docker&#xff0c;也就说要先用docker下载镜像&#xff0c…

[maven] 实现使用 plugin 及 properties 简述

[maven] 实现&使用 plugin 及 properties 简述 这章内容&#xff0c;我个人感觉可看可不看……&#xff1f; 不过课都上了&#xff0c;笔记 &#x1f4d2; 补完才对得起自己嘛 plugins 主要讲一下 maven 的 plugin 时怎么实现的&#xff0c;以及项目中怎么调用自己实现…

成集云 | 用友T+集成聚水潭ERP(用友T+主管供应链)| 解决方案

源系统成集云目标系统 方案介绍 用友T是一款由用友畅捷通推出的新型互联网企业管理系统&#xff0c;它主要满足成长型小微企业对其灵活业务流程的管控需求&#xff0c;并重点解决往来业务管理、订单跟踪、资金、库存等管理难题。 聚水潭是一款以SaaS ERP为核心&#xff0c;集…

mysql事务测试

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

微信管理系统可以解决什么问题?

微信作为一款社交通讯软件&#xff0c;已经成为人们日常生活中不可缺少的工具。不仅个人&#xff0c;很多企业都用微信来联系客户、维护客户和营销&#xff0c;这自然而然就会有很多微信账号、手机也多&#xff0c;那管理起来就会带来很多的不便&#xff0c;而微信管理系统正好…

基于径向基神经RBF的空调功率预测,RBF神经网络的详细原理,RBF回归预测代码

目录 完整代码和数据下载链接&#xff1a;基于MATLAB的RBF的空调能耗预测_模糊空调matlab资源-CSDN文库 https://download.csdn.net/download/abc991835105/87833598 RBF的详细原理 RBF的定义 RBF理论 易错及常见问题 RBF应用实例&#xff0c;基于rbf的空调功率预测 代码 结果…

C语言大佬的必杀技---宏的高级用法

C语言大佬的必杀技—宏的高级用法 目录: 字符串化标记的拼接宏的嵌套替换多条语句防止一个文件被重复包含宏和函数的区别 可能大家在学习的时候用得比较少&#xff0c;但是在一些代码量比较大的时候&#xff0c;这样使用&#xff0c;可以大大的提高代码的可读性&#xff0c;…

minio报错should be less than or equal解决方案

minio报错should be less than or equal解决方案 问题背景解决方案Lyric&#xff1a; 当作你的请求 问题背景 在进行minio扩容时&#xff0c;报错 parity validation returned an error: parity 4 should be less than or equal to 2 <- (4, 4), for pool(2nd解决方案 mi…

ModbusTCP 转 Profinet 主站网关控制汇川伺服驱动器配置案例

ModbusTCP Client 通过 ModbusTCP 控制 Profinet 接口设备&#xff0c;Profinet 接口设备接入 DCS/工控机等 兴达易控ModbusTCP转Profinet主站网关&#xff08;XD-ETHPNM20&#xff09;采用数据映射方式进行工作。 使用设备&#xff1a;兴达易控ModbusTCP 转 Profinet 主站网关…

敏捷开发七大步骤和敏捷工具

敏捷开发是一种以人为核心、迭代、循序渐进的开发方法。在敏捷开发中&#xff0c;软件项目的构建被切分成多个子项目&#xff0c;各个子项目的成果都经过测试&#xff0c;具备集成和可运行的特征。敏捷开发并不追求前期完美的设计、完美编码&#xff0c;而是力求在很短的周期内…

2023-9-23 区间分组

题目链接&#xff1a;区间分组 #include <iostream> #include <algorithm> #include <queue>using namespace std;const int N 100010;struct Range {int l, r;bool operator< (const Range &W) const {return l < W.l;} }range[N];int main() {i…