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

news2024/11/28 3:41:08

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操作延迟

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

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

相关文章

Gossip协议

Gossip协议 一、Gossip协议1.1 工作原理1.2 Gossip优点1.3 Gossip传播方式1.3.1 Anti-Entropy&#xff08;反熵&#xff09;1.3.2 Rumor-Mongering&#xff08;谣言传播&#xff09;1.3.3 结合 1.4 Gossip协议的通信方式1.4.1 Push1.4.2 Pull1.4.3 Push&Pull 二、手撸简易版…

媒介盒子:医疗软文怎么写才能实现营销效果?

随着互联网的快速发展,医疗行业也逐渐意识到了网络营销的重要性。而作为网络营销的一种形式,医疗软文在传播医疗知识、宣传医疗品牌方面具有独特的优势。本文将从选题、内容、形式等多个方面进行探讨&#xff0c;如何写一篇有效的医疗营销软文&#xff1f; 1、选题非常关键 首…

Python“牵手”天猫商品列表数据,关键词搜索天猫API接口数据,天猫API接口申请指南

天猫平台API接口是为开发电商类应用程序而设计的一套完整的、跨浏览器、跨平台的接口规范&#xff0c;天猫API接口是指通过编程的方式&#xff0c;让开发者能够通过HTTP协议直接访问天猫平台的数据&#xff0c;包括商品信息、店铺信息、物流信息等&#xff0c;从而实现天猫平台…

MySql014——分组的GROUP BY子句排序ORDER BYSELECT子句顺序

前提&#xff1a;使用《MySql006——检索数据&#xff1a;基础select语句&#xff08;使用products表、查询单列、多列、所有列、DISTINCT去除重复行、LIMIT限制返回结果的行数、了解完全限定&#xff09;》中创建的products表 一、GROUP BY子句基础用法 SELECT vend_id, COU…

【Debug】解决RecursionError: maximum recursion depth exceeded in comparison报错

&#x1f680;Debug专栏 目录 &#x1f680;Debug专栏 ❓❓问题&#xff1a; &#x1f527;&#x1f527;分析&#xff1a; &#x1f3af;&#x1f3af;解决方案&#xff1a; ❓❓问题&#xff1a; 循环中报错RecursionError: maximum recursion depth exceeded in compari…

IC设计各岗位收入水平对比,看看哪个更适合你?

根据人才招聘平台对于2023年已有数据统计&#xff0c;芯片工程师岗位均薪为26012元&#xff0c;位列全行业第一。 这里需要说明一下&#xff0c;这里的“芯片工程师”涵盖了设计、制造、封测等多环节岗位。并非只有芯片设计岗。 从行业招聘薪酬同比增速来看&#xff0c;电子技…

Bigemap在路桥行业是怎么应用的?

选择Bigemap的原因&#xff1a; 奥维下架了&#xff0c;后来了解到的bigemap&#xff0c;于是测试了这款软件 使用场景&#xff1a; 下载影像、矢量路网做前期策划&#xff0c;下载完数据后导出cad ,做一些标注&#xff0c;最终出图下载等高线&#xff0c;作为前期选址依据 …

Linux环境下SVN服务器的搭建与公网访问:使用cpolar端口映射的实现方法

文章目录 前言1. Ubuntu安装SVN服务2. 修改配置文件2.1 修改svnserve.conf文件2.2 修改passwd文件2.3 修改authz文件 3. 启动svn服务4. 内网穿透4.1 安装cpolar内网穿透4.2 创建隧道映射本地端口 5. 测试公网访问6. 配置固定公网TCP端口地址6.1 保留一个固定的公网TCP端口地址6…

在日本做程序员能攒到钱吗?

如果你就是无欲无求&#xff0c;和人合租&#xff0c;自己做饭&#xff0c;不买高级食材&#xff0c;没有业余爱好&#xff0c;那我可以肯定告诉你一定能攒下钱&#xff0c;问题你是吗&#xff1f;能不能攒下钱丰俭由人&#xff0c;拿的少也有人能攒下钱&#xff0c;拿的多的也…

安防视频监控平台EasyNVR视频监控汇聚平台页面无法上传授权文件的问题解决方案

TSINGSEE青犀视频安防监控平台EasyNVR可支持设备通过RTSP/Onvif协议接入&#xff0c;并能对接入的视频流进行处理与多端分发&#xff0c;包括RTSP、RTMP、HTTP-FLV、WS-FLV、HLS、WebRTC等多种格式。在智慧安防等视频监控场景中&#xff0c;EasyNVR可提供视频实时监控直播、云端…

Viobot硬件控制

一.使用上位机控制 TOF版本设备点击TOF ON即可开启TOF&#xff0c;开启后按键会变成TOF OFF&#xff0c;点击TOF OFF即可关闭TOF 补光灯版本设备点击LED ON即可开启LED &#xff0c;开启后按键会变成LED OFF&#xff0c;点击LED OFF即可关闭LED 设置页面的viobot栏&#xff0c…

13.redis集群、主从复制、哨兵

1.redis主从复制 主从复制是指将一台redis服务器&#xff08;主节点-master&#xff09;的数据复制到其他的redis服务器&#xff08;从节点-slave&#xff09;&#xff0c;默认每台redis服务器都是主节点&#xff0c;每个主节点可以有多个或没有从节点&#xff0c;但一个从节点…

代码随想录算法训练营第五十二天|LeetCode 84.柱状图中最大的矩形

LeetCode 84.柱状图中最大的矩形 代码如下&#xff08;Java&#xff09;&#xff1a;暴力解法 class Solution {public int largestRectangleArea(int[] heights) {int length heights.length;int[] minLeftIndex new int[length];int[] minRightIndex new int[length];min…

Java实现根据关键词搜索1688商品新品数据方法,1688API节课申请指南

要通过1688的API获取商品新品数据&#xff0c;您可以使用1688开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例&#xff0c;展示如何通过1688开放平台API获取商品新品数据&#xff1a; 首先&#xff0c;确保您已注册成为1688开放平台的开发者&#xff0c;并创…

【猿灰灰赠书活动 - 03期】- 【RHCSA/RHCE 红帽Linux认证学习指南(第7版) EX200 EX300】

说明&#xff1a;博文为大家争取福利&#xff0c;与清华大学出版社合作进行送书活动 图书&#xff1a;《RHCSA/RHCE 红帽Linux认证学习指南(第7版) EX200 & EX300》 一、好书推荐 图书介绍 《RHCSA/RHCE 红帽Linux认证学习指南&#xff08;第7版&#xff09; EX200 & E…

亚马逊云科技CEO分享企业内决策流程、领导力原则与重组下的思考

亚马逊云科技首席执行官Adam Selipsky几乎从一开始就在那里&#xff1a;他于2005年加入&#xff0c;在效力亚马逊11年后于2016年离开&#xff0c;转而经营Tableau&#xff0c;并于2021年成为亚马逊云科技首席执行官。当时亚马逊云科技前首席执行官安迪贾西(Andy Jassy)接替杰夫…

从数据孤岛到企业xPA的演化

“数据孤岛”一直以来是企业在信息化进程中面临的比较头疼的问题&#xff0c;由于数据独立存在于不同部门之中&#xff0c;无法进行相互联动&#xff0c;致使数据库无法兼容&#xff0c;这无形中加大了跨部门合作的沟通成本。在此背景下&#xff0c;一种新兴的规划方法——扩展…

知虾shopee数据分析工具:shopee出单的商机利器

当今数字化时代&#xff0c;数据已经成为商业成功的关键要素之一。而Shopee作为东南亚最大的电商平台之一&#xff0c;其强大的数据分析工具正为商家提供了宝贵的市场洞察和决策支持。本文将深入探讨Shopee数据分析工具如何帮助商家抓住商机并取得成功。 洞察消费者需求&#x…

Android网络请求,全方位优雅解析

网络请求的基本流程 网络请求步骤&#xff08;用户输入一个网址到网页最终展现到用户面前&#xff09;大致流程总结如下&#xff1a; 在客户端浏览器中输入网址URL。发送到DNS(域名服务器)获得域名对应的WEB服务器的IP地址。客户端浏览器与WEB服务器建立TCP(传输控制协议)连接…

【MySQL】引擎类型

与其他DBMS一样&#xff0c;MySQL有一个 具体管理和处理数据的内部引擎 。在使用create table语句时&#xff0c;该引擎具体创建表&#xff0c;而在使用select或进行其他数据库处理时&#xff0c;该引擎在内部处理你的请求。多数时候&#xff0c;引擎都隐藏在DBMS内&#xff0…