【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));
    //最终返回的是工作内存的初始值。因此需要在外面再+1
    return 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 guarantees
    this.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操作延迟

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

cookie和session区别

一.Cookie详解 &#xff08;1&#xff09;Cookie是什么 &#xff1f; Cookie&#xff0c;有时也用其复数形式Cookies。类型为“小型文本文件”&#xff0c;是某些网站为了辨别用户身份&#xff0c;进行Session跟踪而储存在用户本地终端上的数据&#xff08;通常经过加密&#…

北斗星耀,导航天下:中国北斗卫星导航系统的发展历程与应用现状

听到“北斗”这个词&#xff0c;你会想到什么&#xff1f; 若是在古代&#xff0c;人们会想到闪耀在夜空的北斗七星。 而在现代&#xff0c;很多人会脱口而出“北斗卫星”。我国蓬勃发展的现代航天事业&#xff0c;为“北斗”赋予了一层新的含义。 北斗卫星导航系统&#xff08…

C#调用barTender打印标签示例

使用的电脑需要先安装BarTender 我封装成一个类 using System; using System.Windows.Forms;namespace FT_Tools {public class SysContext{public static BarTender.Application btapp new BarTender.Application();public static BarTender.Format btFormat;public void Q…

Python中的迭代器和生成器介绍

一、迭代器&#xff08;Iterators&#xff09; 迭代器是Python中用于遍历数据集合的一种机制。它是一个实现了迭代协议的对象&#xff0c;可以通过iter()函数来获得迭代器。迭代器需要实现两个方法&#xff1a;__iter__()和__next__()。其中&#xff0c;__iter__()返回迭代器自…

探秘二叉树后序遍历:从叶子到根的深度之旅

本篇博客会讲解力扣“145. 二叉树的后序遍历”的解题思路&#xff0c;这是题目链接。 本题的思路是&#xff1a; 先创建一个数组&#xff0c;用来存储二叉树后序遍历的结果。数组的大小跟树的结点个数有关。树的结点个数可以使用递归实现&#xff0c;即总个数左子树结点个数右…

【C++】多态学习

多态 多态的概念与定义多态的概念构成多态的两个条件虚函数与重写重写的两个特例 final 和 override重载、重写(覆盖)、重定义(隐藏)的对比抽象类多态的原理静态绑定与动态绑定 单继承与多继承关系下的虚函数表(派生类)单继承中的虚函数表查看多继承中的虚函数表查看 菱形继承与…

Unity3d:GameFramework解析:实体,对象池,资源管理,获取计数,引用计数,自动释放

基本概念 1.GF万物基于引用池IReference 2.ObjectBase : IReference类的m_Target持有unity中Mono&#xff0c;资源&#xff0c;GameObejct 3.AssetObject : ObjectBase类m_Target持有Assetbundle中的Asset&#xff0c;具有获取&#xff0c;引用两个计数管理释放 4.ResourceObj…

sql:SQL优化知识点记录(三)

&#xff08;1&#xff09;explain之select_type和table介绍 简单的查询类型是&#xff1a;simple 外层 primary&#xff0c;括号里subquery 用到了临时表&#xff1a;derived &#xff08;2&#xff09;explain之type介绍 trpe反映的结果与我们sql是否优化过&#xff0c;是否…

从零起步:学习数据结构的完整路径

文章目录 1. 基础概念和前置知识2. 线性数据结构3. 栈和队列4. 树结构5. 图结构6. 散列表和哈希表7. 高级数据结构8. 复杂性分析和算法设计9. 实践和项目10. 继续学习和深入11. 学习资源12. 练习和实践 &#x1f389;欢迎来到数据结构学习专栏~从零起步&#xff1a;学习数据结构…

电气特征分析(ESA)技术是什么及有何应用场景

在现代工业领域&#xff0c;电机扮演着不可或缺的角色&#xff0c;驱动着生产设备的正常运行。然而&#xff0c;电机的故障可能会导致生产中断、计划外停机以及高昂的经济损失。为了保障生产的连续性和效率&#xff0c;预测性维护变得至关重要。在这个背景下&#xff0c;电气特…

HTML基础--标签

目录 列表标签 有序列表 type属性 有序列表嵌套 无序列表 type属性 无序列表嵌套 常见应用场景 表格标签 表格展示效果 表格属性 表格单元格合并 单元格合并属性 列表标签 HTL作为构建网页内容的标记语言&#xff0c;提供了多种列表标签&#xff0c;用于在网页中展…

如何使用CSS实现一个3D旋转效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 3D效果实现⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域…

实战教学:农产品小程序商城的搭建与运营

随着移动设备的普及和互联网技术的发展&#xff0c;小程序商城已经成为农产品销售的一种新兴渠道。本文将以乔拓云网为平台&#xff0c;详细介绍如何搭建和运营农产品小程序商城。 步骤一&#xff1a;登录乔拓云网后台 首先&#xff0c;进入乔拓云网站后台&#xff0c;找到并点…

Vite打包性能优化及填坑

最近在使用 Vite4.0 构建一个中型前端项目的过程中&#xff0c;遇到了一些坑&#xff0c;也做了一些项目在构建生产环境时的优化&#xff0c;在这里做一个记录&#xff0c;以便后期查阅。(完整配置在后面) 上面是dist文件夹的截图&#xff0c;里面的内容已经有30mb了&#xff…

LNMP 平台搭建(四十)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 搭建LNMP 一、安装Nginx 二、安装Mysql 三、安装PHP 四、部署应用 前言 LNMP平台指的是将Linux、Nginx、MySQL和PHP&#xff08;或者其他的编程语言&#xff0c;如…

成都智慧企业发展研究院总经理郑小华:践行双轮驱动,为能源电力数智化注入新活力丨数据猿专访...

大数据产业创新服务媒体 ——聚焦数据 改变商业 随着全球经济走向数字化&#xff0c;中国正处于这一浪潮的前沿&#xff0c;进行前所未有的技术与产业深度融合。政府在2023年2月印发的《数字中国建设整体布局规划》等政策下&#xff0c;明确展示了对数字经济的支持与鼓励&…

基于“R语言+遥感“水环境综合评价方法教程

详情点击链接&#xff1a;基于"R语言遥感"水环境综合评价方法教程 一&#xff1a;R语言 1.1 R语言特点&#xff08;R语言&#xff09; 1.2 安装R&#xff08;R语言&#xff09; 1.3 安装RStudio&#xff08;R语言&#xff09; &#xff08;1&#xff09;下载地址…

Android studio实现圆形进度条

参考博客 效果图 MainActivity import androidx.appcompat.app.AppCompatActivity; import android.graphics.Color; import android.os.Bundle; import android.widget.TextView;import java.util.Timer; import java.util.TimerTask;public class MainActivity extends App…

rknn_toolkit以及rknpu环境搭建-rv1126

rknn_toolkit安装------------------------------------------------------------------------------- 环境要求&#xff1a;ubutu18.04 建议使用docker镜像 安装docker 参考https://zhuanlan.zhihu.com/p/143156163 镜像地址 百度企业网盘-企业云盘-企业云存储解决方案-同…

Vue3.0 新特性以及使用变更总结

Vue3.0 在2020年9月正式发布了&#xff0c;也有许多小伙伴都热情的拥抱Vue3.0。去年年底我们新项目使用Vue3.0来开发&#xff0c;这篇文章就是在使用后的一个总结&#xff0c; 包含Vue3新特性的使用以及一些用法上的变更。 图片.png 为什么要升级Vue3 使用Vue2.x的小伙伴都熟悉…