【JUC系列-03】熟练掌握Atomic原子系列基本使用

JUC系列整体栏目


内容链接地址
【一】深入理解JMM内存模型的底层实现原理https://zhenghuisheng.blog.csdn.net/article/details/132400429
【二】深入理解CAS底层原理和基本使用https://blog.csdn.net/zhenghuishengq/article/details/132478786
【三】熟练掌握Atomic原子系列基本使用https://blog.csdn.net/zhenghuishengq/article/details/132543379

熟练掌握Atomic原子系列基本使用

  • 一,深入理解atomic原子系列基本操作
    • 1,初识atomic原子系列
    • 2,CAS的方式实现atomic原子类的底层
    • 3,五种数据类型的基本使用
      • 3.1,基本数据类型
      • 3.2,数组数据类型
      • 3.3,引用数据类型
      • 3.4,对象属性修改器
      • 3.5,原子类型累加器(重点)

一,深入理解atomic原子系列基本操作

1,初识atomic原子系列

在jvm单进程中,往往会涉及到在多线程下一些关于数据的增加的问题,如典型的数据类加问题,通常情况是可以直接采用悲观锁 synchronized 关键字来实现的,但是由于悲观锁需要涉及到用户态到内核态直接的切换,会严重的影响该场景下的性能问题,因此在后面,通过cas底层实现的atomic算法就此而生。

在java.util.concurrent下面有一个atomic的原子包,里面有着多个关于atomic的原子实现类,atomic主要能实现的数据类型可以归纳为五种:基本数据类型、引用数据类型、数组数据类型、对象属性修改器、原子类型累加器

在这里插入图片描述

2,CAS的方式实现atomic原子类的底层

在上一篇中,谈到了cas的底层实现,主要是通过内部自旋加调用硬件层面的指令来实现数据的原子性,通过cmpxchg 指令来实现比较和交换的操作,从而实现总线加锁,并通过一个 #lock 前缀指令来实现storeLoad内存屏障的功能,从而解决在多线程中共享变量的可见性、原子性和有序性

在atomic中,其底层实现就是通过cas的原理来实现的,由于cas的缺点之一就是只能操作一个变量,atomic原子包的主要思想就是对单个变量进行操作,因此atomic采用cas作为底层实现最好不过,并且可以减少用户态到内核态之间的切换,在一定的数据范围内,其效率是远远高于这个synchronized这些锁的

如初始化一个 AtomicInteger 原子类,如下

AtomicInteger atomicInteger = new AtomicInteger(0);

接下来对这个类进行一个自增的操作,就是调用其 incrementAndGet 方法

//先自增,再将值放回
atomicInteger.incrementAndGet()

其底层的实现如下,会通过一个unsafe类的一个实例,unsafe类就是介于java类和硬件层面打交道的类

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

随后再查看这个unsafe类调用的这个 getAndAddInt 方法,很明显,这个方法就是比较和交换的底层实现

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {//var5 工作内存中的初始值,就是旧值var5 = this.getIntVolatile(var1, var2);//var1 当前值所占的字节数      var2 offset偏移量//var5 + var4 累加完的值} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));//最终返回的是工作内存的初始值。因此需要在外面再+1return var5;
}

而最终调用这个 native 本地方法栈中的 compareAndSwapInt 方法,再去调用底层的硬件实现比较和交换的操作。在本人上一篇cas的文章中,有着更为详细的描述。

public final native boolean compareAndSwapInt(Object var1,long var2,int var4,int var5);

3,五种数据类型的基本使用

3.1,基本数据类型

基本数据类型主要有:AtomicInteger、AtomicBoolean、AtomicLong 这三种,以AtomicInteger来举例,其用法主要如下

//初始化 AtomicInteger 对象
AtomicInteger atomicInteger = new AtomicInteger(0);

在这个AtomicInteger类中,里面可以使用的方法主要如下图,如一些getAndAdd,addAndGet,getAndIncrement,getAndDecrement,incrementAndGet,deCrementAndGet等等。都会涉及到是先自增在获取值还是先获取值再自增的操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ikbbfrI2-1693214340918)(img/1693187864610.png)]

相关的AtomicInteger类的api的使用命令如下

/*** @author zhenghuisheng* @date : 2023/8/28*/
public class AtomicIntegerTest {public static void main(String[] args) {//初始化 AtomicInteger 对象AtomicInteger atomicInteger = new AtomicInteger(0);System.out.println("先获取再自增:" + atomicInteger.getAndIncrement());System.out.println("先自增再获取:" + atomicInteger.incrementAndGet());System.out.println("比较和交换值:" + atomicInteger.compareAndSet(2, 10));System.out.println("读取当前值为:" + atomicInteger.get());System.out.println(atomicInteger.intValue());System.out.println("先自增再获取:" + atomicInteger.addAndGet(10));atomicInteger.set(5);System.out.println("读取当前值为:" + atomicInteger.get());// lazySet在多线程的场景下不能保证缓存立马被刷新atomicInteger.lazySet(10);System.out.println("读取当前值为:" + atomicInteger.get());}
}

3.2,数组数据类型

数组数据类型主要有:AtomicIntegerArray、AtomicReferenceArray、AtomicLongArray这三种数据类型

接下来再以这个 AtomicIntegerArray 为例,首先先创建一个AtomicIntegerArray对象和一个整型数组

int[] currentData = {1,2,3,4};
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(currentData);

在这个 AtomicIntegerArray 类的构造方法中,会克隆出一个新的数组,所以在获取数据得调用get方法

public AtomicIntegerArray(int[] array) {// Visibility guaranteed by final field guaranteesthis.array = array.clone();
}

通过下图可知 AtomicIntegerArray 的方法其实和AtomicInteger的类似,只是操作的对象不同

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5EAu5ID9-1693214340918)(img/1693202518345.png)]

相关AtomicIntegerArray类的api的使用如下

public class AtomicIntegerArrayTest {public static void main(String[] args) {//定义一个数组int[] currentData = {1,2,3,4};AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(currentData);System.out.println("获取数组下标为1的值:" + atomicIntegerArray.get(1));System.out.println("获取数组下标为2的值:" + atomicIntegerArray.incrementAndGet(2));//比较和交换System.out.println(atomicIntegerArray.compareAndSet(3, 4, 5));System.out.println(atomicIntegerArray.get(3));//先累加,再将值放回System.out.println(atomicIntegerArray.addAndGet(1, 10));//在多线程中不会立即刷新缓存,不能保证可见性atomicIntegerArray.lazySet(2,10);System.out.println(atomicIntegerArray.get(2));}
}

3.3,引用数据类型

引用数据类型的主要有:AtomicReference、AtomicStampedRerence、AtomicMarkableReference类,

接下来依旧以这个AtomicReference类作为实例,先创建一个 AtomicReference类实例

//原子类
AtomicReference<Student> objectAtomicReference = new AtomicReference<>();

Student类比较简单,只有两个属性,分别是name和age

class Student{String name;int age;public Student(String name,int age){this.name = name;this.age = age;}
}

在使用这个类之前,先查看一下这个类里面有哪些方法,以及变量等

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VZGSQ5MR-1693214340919)(img/1693206590488.png)]

接下来详细的描述一下该类的具体是如何使用的

public static void main(String[] args) {//原子类AtomicReference < Student > objectAtomicReference = new AtomicReference < > ();Student stu1 = new Student("zhenghuisheng", 18);Student stu2 = new Student("zhansan", 22);//设置值objectAtomicReference.set(stu1);System.out.println(objectAtomicReference.get().name + "---" + objectAtomicReference.get().age);//比较和交换System.out.println(objectAtomicReference.compareAndSet(stu1, stu2));//获取值System.out.println(objectAtomicReference.get().name + "---" + objectAtomicReference.get().age);}

3.4,对象属性修改器

对象属性修改器主要有:AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferencrFieldUpdater 这三个类

以AtomicIntegerFieldUpdater为例子,依旧使用上面那个Student类,接下来创建一个AtomicIntegerFieldUpdater的对象,随后对这个对象里面的属性值age进行操作

//初始化 AtomicIntegerFieldUpdater 对象
static AtomicIntegerFieldUpdater<Student> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
public static void main(String[] args) {//初始化对象Student stu1 = new Student("zhenghuisheng", 18);atomicIntegerFieldUpdater.set(stu1,18);System.out.println(atomicIntegerFieldUpdater.get(stu1));System.out.println(atomicIntegerFieldUpdater.addAndGet(stu1, 10));System.out.println(atomicIntegerFieldUpdater.incrementAndGet(stu1));
}

3.5,原子类型累加器(重点)

原子类型累加器主要有以下五种类型,分别是:Striped64、DoubleAccumulator、LongAccumulator、LongAdder、DoubleAdder,这种类型的数据是在jdk1.8之后才新增的类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMIG3Awa-1693214340920)(img/1693208753361.png)]

由于atomic底层是通过这个cas实现的,但是cas也存在着一个缺陷,就是不利于在并发量很大的场景下使用,因为自旋会随着大量的比较和交换耗废大量的cpu资源,但是在jdk8开始,就引入了一个重要的算法:写热点分散

cas主要是单线程执行,因此为了解决这个问题,就可以将一个大的累加操作,拆分成多个小的累加操作,最后再进行汇总累加,这样解决出现在高并发的场景下。总而言之就是先分再合的思想。

举一个简单的例子,假设此时有10000个线程需要进行类加操作,那么这10000个线程就得不断的进行自旋,进行比较和交换的操作,由于底层保证了原子性,因此可以看成就是一个线程执行;现在优化思路就是,将这10000次的类加,拆分成10个数组,每个线程只需要对应一个下标,每个数组的值进行类加,只需要累加1000次,最后进行汇总即可,数据量越大的情况,耗费的时间越短,占用的cpu资源越少。

接下来以这个 LongAdder 为例,首先先实例化一个longAdder对象,随后进行一个自增的操作

LongAdder longAdder = new LongAdder();
// 自增操作
longAdder.increment();

随后查看这个 increment 方法的底层源码,其内部会调用一个add方法,内部会进行比较交互操作

public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) {boolean uncontended = true;if (as == null || (m = as.length - 1) < 0 ||//求余操作,判断会进入哪个槽位(a = as[getProbe() & m]) == null ||//自旋,比较和交换的操作!(uncontended = a.cas(v = a.value, v + x)))//计算longAccumulate(x, null, uncontended);}
}

随后通过调用 longAccumulate 方法,对这些值进行计算的操作,计算的方法如下,内部会涉及到的一些变量也快给列了出来

//处理器的个数,inter中一个cpu对应两个处理器
static final int NCPU = Runtime.getRuntime().availableProcessors();
//拆分数组
transient volatile Cell[] cells;
//如果不存在竞争,则直接在这个比那里上面累加
transient volatile long base;
//加锁的标记
transient volatile int cellsBusy;

接下来直接看这个longAccumulate方法吧,有点复杂,我直接把图片贴出来了…(头痛),感兴趣的大大大佬可以自行研究一下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzHiWfqE-1693214340920)(img/1693212919552.png)]

LongAdder设计的精妙之处:减少热点冲突,尽量将CAS操作延迟

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

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

相关文章

〔017〕Stable Diffusion 之 常用模型推荐 篇

✨ 目录 &#x1f388; 模型网站&#x1f388; 仿真系列&#x1f388; 国风系列&#x1f388; 卡通动漫系列&#x1f388; 3D系列&#x1f388; 一些好用的lora模型 &#x1f388; 模型网站 由于现在大模型超级多&#xff0c;导致每种画风的模型太多&#xff0c;那么如何选择最…

代码随想录算法训练营第四十六天 | 139.单词拆分

代码随想录算法训练营第四十六天 | 139.单词拆分 139.单词拆分 139.单词拆分 题目链接 视频讲解 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典…

9.Redis数据结构之整数数组

Redis中的Set与Java中的HashSet一样&#xff0c;无序且存储元素不重复。 Redis的集合对象Set使用了intset和hashtable两种数据结构存储。intset我们可以理解为数组,hashtable就是普通的哈希表(key为Set集合中元素的值&#xff0c;value为null)。当value是整数值时&#xff0c;且…

中文乱码处理

&#x1f600;前言 中文乱码处理 &#x1f3e0;个人主页&#xff1a;尘觉主页 &#x1f9d1;个人简介&#xff1a;大家好&#xff0c;我是尘觉&#xff0c;希望我的文章可以帮助到大家&#xff0c;您的满意是我的动力&#x1f609;&#x1f609; 在csdn获奖荣誉: &#x1f3c…

自动化备份方案

背景说明 网上有很多教程&#xff0c;写的都是从零搭建一个什么什么&#xff0c;基本上都是从无到有的教程&#xff0c;但是&#xff0c;很少有文章提及搭建好之后如何备份&#xff0c;这次通过请教GitHub Copilot Chat&#xff0c;生成几个备份脚本&#xff0c;以备后用。 注…

linux之《进程》

文章目录 进程基础pcb状态优先级 进程的调度常见的调度算法 进程的通信方式 进程基础 pcb 操作系统在创建进程时&#xff0c;会给进程分配一块PCB&#xff08;process control block 进程控制块&#xff09;&#xff0c;对应linux上就是task_struct结构体&#xff0c;PCB里面…

时序分解 | MATLAB实现基于SVD奇异值分解的信号分解分量可视化

时序分解 | MATLAB实现基于SVD奇异值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于SVD奇异值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 SVD分解重构算法&#xff0c;MATLAB程序&#xff0c;奇异值分解 (Singular Value Decompo…

解析直播美颜SDK功能算法:肤色识别、特征增强与实时渲染

在这个数字化时代&#xff0c;美颜技术在直播中的应用愈发受到重视&#xff0c;为主播和观众创造更加美好的视觉体验。本文将深入探讨直播美颜SDK 的核心功能算法&#xff0c;包括肤色识别、特征增强与实时渲染&#xff0c;揭示其背后的技术原理与工作机制。 一、肤色识别算法…

STM32电源名词解释

STM32电源架构 常用名词 VCC Ccircuit 表示电路&#xff0c;即接入电路的电压。 VDD Ddevice 表示器件&#xff0c; 即器件内部的工作电压。 VSS Sseries 表示公共连接&#xff0c;通常指电路公共接地端电压。 VDDA Aanalog 表示模拟&#xff0c;是模拟电路部分的电源。主要为…

【Python Flask+Nginx】实现HTTP、WS (两步实现,简单易懂)

目录 一、创建Flask应用 二、部署Nginx 2.1 下载Nginx 2.2 修改Nginx配置文件 2.3 启动Nginx 三、测试 一、创建Flask应用 首先我写了如下一个基于Flask的Demo&#xff0c;该Demo包含两个接口一个是HTTP接口&#xff08;http://127.0.0.1:5000&#xff09;&#xff0c…

[ACL2023] Exploring Lottery Prompts for Pre-trained Language Models

Exploring Lottery Prompts for Pre-trained Language Models 文章链接 清深的工作&#xff0c;比较有意思的一篇。作者先给出假设&#xff0c;对于分类问题&#xff0c;在有限的语料空间内总能找到一个prompt让这个问题分类正确&#xff0c;作者称之为lottery prompt。为此&…

Axure RP软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Axure RP是一款专业的原型设计工具&#xff0c;它能够帮助用户创建高保真度的交互式原型。 Axure RP具有以下特点&#xff1a; 强大的交互设计功能&#xff1a;Axure RP提供了丰富的交互设计工具&#xff0c;用户可以通过拖拽和…

17.2 【Linux】通过 systemctl 管理服务

systemd这个启动服务的机制&#xff0c;是通过一支名为systemctl的指令来处理的。跟以前 systemV 需要 service / chkconfig / setup / init 等指令来协助不同&#xff0c; systemd 就是仅有systemctl 这个指令来处理而已。 17.2.1 通过 systemctl 管理单一服务 &#xff08;s…

Python 中具有漂移的指数布朗运动;模拟股票价格的未来分布,以预测股票的未来价值

一、说明 随机过程是由概率定律生成的一系列事件或路径。也就是说&#xff0c;随机事件可以随着时间的推移而发生&#xff0c;但受特定的统计和概率规则的约束。主要的随机过程是随机游走或布朗运动。这个过程可以用来预测许多变量&#xff0c;这些变量似乎遵循随机趋势&#x…

[当前就业]2023年8月25日-计算机视觉就业现状分析

计算机视觉就业现状分析 前言&#xff1a;超越YOLO&#xff1a;计算机视觉市场蓬勃发展 如今&#xff0c;YOLO&#xff08;You Only Look Once&#xff09;新版本的发布周期很快&#xff0c;每次迭代的性能都优于其前身。每 3 到 4 个月就会推出一个升级版 YOLO 变体&#xf…

​ 模拟嵌入式边缘计算卡设计方案:367-XC7Z100 板卡 基于zynq XC7Z100 FMC接口通用计算平台

基于zynq XC7Z100 FMC接口通用计算平台 一、板卡概述 北京太速科技板卡由SoC XC7Z100-2FFG900I芯片来完成卡主控及数字信号处理&#xff0c;XC7Z100内部集成了两个ARM Cortex-A9核和一个kintex 7的FPGA&#xff0c;通过PL端FPGA扩展FMC、光纤、IO等接口&#xff0c;PS端ARM扩展…

信看课堂笔记—LDO和DC-DC电路打PK

LDO&#xff08;low dropout voltage regulator&#xff0c;低压差线性稳压器&#xff09;和DC-DC(Direct current-Direct current converter&#xff0c;直流电压转直流电压转换器)电源是非常常见的电源电路&#xff0c;LDO 出来的比较早&#xff0c;像老戏骨一样&#xff0c;…

汽车电子笔记之:基于AUTOSAR的多核监控机制

目录 1、概述 2、系统监控的目标 2.1、任务的状态机 2.2、任务服务函数 2.3、任务周期性事件 2.4、时间监控的指标 2.5、时间监控的原理 2.6、CPU负载率监控原理 2.6.1、设计思路 2.6.2、监控方法的评价 3、基于WDGM模块热舞时序监控方法 3.1、活跃监督 3.2、截至时…

在VScode中执行npm、yarn命令报错解

在VScode中执行npm、yarn命令报错解 我使用的是vnm安装好npm&#xff0c;在WindowsR 界面是可以运行查看出版本的&#xff1b;但是在VScode中报错。 查了很多资料&#xff0c;我这种情况的原因是在VScode中默认使用的终端是Powershell&#xff0c;然后我切换到系统的cmd则可以…

springMVC之视图

文章目录 前言一、ThymeleafView二、转发视图三、重定向视图四、视图控制器view-controller五、补充总结 前言 SpringMVC中的视图是View接口&#xff0c;视图的作用渲染数据&#xff0c;将模型Model中的数据展示给用户。 SpringMVC视图的种类很多&#xff0c;默认有转发视图和…