LongAdder/LongAccumulator类分析

news2024/11/19 12:36:53

一、LongAdder简介

1.下图是JDK-API文档的的截图信息

我们可以得知这两个类都是1.8开始提供的,并且都具有顶级的并发性。这两类的区别点主要在于LongAdder初始值为0,只能做累加操作,而LongAccumulator可以完成一些复杂的计算,本文主要以LongAdder作为核心来讲解。
在这里插入图片描述在这里插入图片描述

2.LongAdder常用的api如下:

在这里插入图片描述

3.性能优势demo

class ClickNumber{
    int number = 0;
    public synchronized void clickBySynchronized(){
        number++;
    }
    AtomicLong atomicLong = new AtomicLong(0);
    public void clickByAtomicLong(){
        atomicLong.getAndIncrement();
    }
    LongAdder longAdder = new LongAdder();
    public void clickByLongAdder(){
        longAdder.increment();
    }
    LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,0);
    public void clickByLongAccumulator(){
        longAccumulator.accumulate(1);
    }
}
public class Test {
 	public static final int _1W = 10000;
    public static final int threadNumber = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long startTime;
        long endTime;
        CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
       
        startTime = System.currentTimeMillis();
        for (int i = 0; i < threadNumber; i++) {
            new Thread(() ->{
                try{
                    for (int i1 = 0; i1 < 100 * _1W; i1++) {
                        clickNumber.clickBySynchronized();
                    }
                }finally {
                    countDownLatch1.countDown();
                }
            }).start();
        }
        countDownLatch1.await();
        endTime =  System.currentTimeMillis();
        System.out.println("synchronize花费:"+(endTime - startTime) + "毫秒");
    }
}

在这里插入图片描述
其结果展示了在高并发的状态下,LongAdder和LongAccumulator完爆上面两种高并发累加,AutomicLong性能与之差了9-10倍。(因为其底层减少了乐观锁的重试次数)




二、原因分析

1.类继承分析

LongAdder是striped64的子类,该64类是性能暴虐其他原子类的灵魂
在这里插入图片描述
1.下图是Striped64的重要成员变量和方法解析
在这里插入图片描述
在这里插入图片描述
3.先说结论:
在这里插入图片描述
在这里插入图片描述

2.LongAdder类源码分析

1.分析add方法

在这里插入图片描述

 public void add(long x) {
	  //as是striped64中cells数组属性
	  //b是striped64中的base属性
	  //v是当前线程hash到的cell中存储的值
	  //m是cells的长度减1
	  //a是当前线程hash命中的cells单元格
      Cell[] as; long b, v; int m; Cell a;
      //首次首线程((as == cells) != null)一定是false,此时走casBase方法,以CAS的方式更新base值,且只有当cas失败时,才会走到if中
      //条件1: cells不为空
      //条件2: cas操作base失败,说明其他线程先一步修改了base,正在出现竞争
      if ((as = cells) != null || !casBase(b = base, b + x)) {
      	  // true无竞争 false表示竞争激烈,多个线程hash到同一个cell,可能要扩容
          boolean uncontended = true;
          //条件1:cells为空(第一次进来肯定符合条件,于是进行longAccumulate中创建cells[])
          //条件2:应该不会出现
          //条件3:当前线程所在的cell为空,说明当前线程还没有更新过cell,应该初始化一个cell
          //条件4:更新当前线程所在的cell失败,说明现在竞争很激烈,多个线程hash到了同一个cell,应扩容
          if (as == null || (m = as.length - 1) < 0 ||
          	  //getProbe()方法返回的是线程中threadLocalRandomProbe字段
          	  //它是通过随机数生成的一个值,对于一个确定的线程这个值是固定的(除非刻意修改它)
              (a = as[getProbe() & m]) == null ||
              !(uncontended = a.cas(v = a.value, v + x)))
              longAccumulate(x, null, uncontended);//调用striped64中的方法处理
      }
  }

总结:
1.如果cells表为空,尝试用CAS更新base字段,成功则退出
2.如果cells表为空,CAS更新base字段失败,出现竞争,uncontended为true,调用longAccumulate(初始化大小为2的cells数组,并把当前线程hash命中的槽位初始化)
3.如果cells表非空,但当前线程映射的槽为空,uncontended为true,调用longAccumulate(初始化该槽位的cell)
4.如果cells表非空,且当前线程映射的槽非空,CAS更新cell的值,成功则返回,否则,uncontended设为false,调用longAccumulate(先尝试让当前线程换个槽再来一次cas,如果依旧失败则对cells数组进行扩容)

2.分析longAccumulate

在这里插入图片描述

    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        // 存储线程的probe值                      
        int h;
        // 如果getProbe()方法返回0,说明随机数未初始化
        if ((h = getProbe()) == 0) { // 这个if相当于给当前线程生成一个非0的hash值
            // 使用 ThreadLocalRandom 为当前线程重新计算一个hash值,强制初始化
            ThreadLocalRandom.current(); // force initialization
            // 重新获取probe值,hash值被重置就好比一个全新的线程一样,所以设置了wasUncontended竞争状态为true。
            h = getProbe();
            // 重新计算了当前线程的hash后认为此次不算是一次竞争,都未初始化,肯定还不存在竞争激烈,wasUncontended竞争状态为true
            wasUncontended = true;
        }
        //如果hash取模映射到得到的cell单元不是null,则为true,此值也可以看作是扩容意向
        boolean collide = false;                // True if last slot nonempty
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            // CASE1: cells已经被初始化了
            if ((as = cells) != null && (n = as.length) > 0) {
                // 前面add方法中第二个if判断的条件3成立就会走下面的这个if里面的逻辑
                if ((a = as[(n - 1) & h]) == null) {     // 当前线程的hash值运算后映射得到的Cell单元为null,说明cell没有被使用
                    if (cellsBusy == 0) {                // cell[]数组没有正在扩容  Try to attach new Cell
                        Cell r = new Cell(x);            // 创建一个cell单元       Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {     // 尝试加锁,成功后 cellBusy == 1
                            boolean created = false;
                            try {                                   // 在有锁的情况下再检查一遍之前的判断  Recheck under lock
                                Cell[] rs; int m, j;                // 将cell单元附到Cell[]数组上
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;
                                    created = true;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // wasUncontended表示第一次CAS更新Cell单元是否成功了   CAS already known to fail
                    wasUncontended = true;      // 重新置为true,后面会调用”h = advanceProbe(h);“重新计算线程的hash值。由于此处没有break,再次循环会进入下面的if           Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))  // 尝试CAS更新cell单元格,cas成功则直接跳出循环(能进入改行说明当前线程对应的数组中有了数据,也重置过hash值)
                    break;
                else if (n >= NCPU || cells != as)                    // 当Cell数组的大小已经超过CPU核数后,永远不会再进行扩容
                    collide = false;                                  // 扩容标识,置为false,表示不会再进行扩容,并重新计算当前线程的hash值继续循环       At max size or stale
                else if (!collide)
                    collide = true;                                   // 如果扩容意向collide是false,则重新计算当前线程的hash值继续循环
                else if (cellsBusy == 0 && casCellsBusy()) {          // 尝试加锁进行扩容
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];             // 扩容后的大小 ==  当前容量 * 2
                            for (int i = 0; i < n; ++i)               // 扩容后再将之前数组的元素拷贝到新数组中
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                // 重置当前线程的hash值
                h = advanceProbe(h);
            }
            // CASE2:cells没有加锁且没有初始化,则尝试对它进行加锁,并初始化cells的值
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    // 如果不进行double check,就会再次new一个cell数组,上一个线程对应数组中的值将会被篡改
                    if (cells == as) {
                        // 新建一个大小为2的Cell数组
                        Cell[] rs = new Cell[2];
                        // 找到当前线程hash到数组中的位置并创建其对应的Cell,x的默认值是1
                        // h & 1 类似于我们之前HashMap常用到的计算散列桶index的算法,通常都是hash & (tab.len - 1),同hashmap一个意思
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            // CASE3: cells正在进行初始化,则尝试直接在基数base上进行累加操作
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

流程图总结:
在这里插入图片描述在这里插入图片描述

3.分析sum方法

发现底层很简单啊,直接把base和cells[]数组中的每个元素加起来,并且该方法没有加锁
在这里插入图片描述

三、最终总结表

AtomicLong:
在这里插入图片描述

LongAdder:
在这里插入图片描述

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

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

相关文章

软件设计师考试整理-0-前言

1. 整理初衷 2022年下半年的软考成绩出来了&#xff0c;查了一下&#xff0c;上午58分&#xff0c;下午61分&#xff0c;虽然不高&#xff0c;但是也过了&#xff0c;还是值得开心的&#xff0c;毕竟在最初报考的时候&#xff0c;会的知识点寥寥无几。 先解释下为什么我会报考…

富勒烯C60,131159-39-2,水溶性富勒烯,CARBON C60

产品描述&#xff1a;富勒烯C60与金刚石、石墨是碳的三种同素异形体&#xff0c;富勒烯分子是一种由60个碳原子结合形成的稳定分子&#xff0c;它具有60个顶点和32个面&#xff0c;其中12个为正五边形&#xff0c;20个为正六边形&#xff0c;它形似足球&#xff0c;所以又称为富…

OpenMMLab AI实战营Day2 图像分类

目录 一、图像分类 二、卷积神经网络 三、超越ResNet的图像分类模型 1、神经结构搜索 2、Transformer 3、ConvNext 四、轻量化神经网络 五、Vision Transformer 六、模型学习 七、学习率与优化器调整策略 八、数据增强 一、图像分类 图像分类&#xff1a;识别图像中…

网络骗局丨典型案例分析,大家一起来避雷!

作者&#xff1a;黑蛋因为疫情的困扰&#xff0c;总体经济都不是很好&#xff0c;春节前后&#xff0c;网络诈骗高发期&#xff0c;以下是几种典型案例&#xff0c;一起来看看。比较常见的有以下几种&#xff1a;1、网络购物骗局小红在某平台网购一件产品&#xff0c;几天后&am…

Nacos+Springcloud+mybatis-plus+oracle的整合

NacosSpringcloudmybatis-plusoracle的整合 1、项目结构 2、父类的依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&q…

shell脚本基本使用

目录 1.是什么&#xff1f; 2.写法 3.shell脚本语法 3.1第一个shell脚本 3.2交互式shell脚本 3.3shell脚本的数值计算 3.4test命令 3.5中括号【】判断符 3.6默认变量 4.shell脚本条件判断 4.1 if fi 4.2 if then else 4.3 if elif else 4.4 case 5.shell脚本函数…

Linux 学习笔记 正则、管道、进程与任务定时

一、筛选与搜索 1.1 grep 命令 筛选数据 grep 是 Globally search a regular expression and print 的缩写。意思是全局搜索一个正则表达式&#xff0c;并且打印。 考虑这样的一个名为 a.txt 的文件&#xff1a; 执行 grep apple ./a.txt 即可匹配所有含有 apple 的字符 默认…

IDEA搭建Finchley.SR2版本的SpringCloud父子基础项目-------zuul和SpringConfig

1.Zuul是什么 Zuul包含了对请求的路由和过滤两个最主要的功能&#xff1a; 其中路由功能负责将外部请求转发到具体的微服务实例上&#xff0c;是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预&#xff0c;是实现请求校验、服务聚合等功能的基础. Zu…

4款实用的办公软件,每一款都让你效率翻倍,相见不易

称心的电脑使用体验&#xff0c;总离不开实用的工具&#xff0c;一款高效的工具&#xff0c;能让你的办公体验翻倍。 1、Mem Reduct 这是一款强大到离谱的电脑清理工具&#xff0c;比某安全卫士实用多&#xff0c;没有任何弹屏广告&#xff0c;完全免费使用&#xff0c;内存清理…

搜索算法基础

一 DFS 深度优先搜索算法&#xff08;Depth First Search&#xff0c;简称DFS&#xff09;&#xff1a;一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点&#xff0c;尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件&#xff0c;搜…

uboot的烧写及使用

目录 一、uboot概述 Bootloader Bootloader基本功能 常见的Bootloader 二、SD卡启动盘制作 三、uboot的使用 3.1uboot模式 3.2uboot帮助命令 3.3uboot环境变量命令 3.4常用环境变量 3.5网络传输命令 3.6u-boot访问存储器命令 3.7 u-boot自启动环境变量&#xff08…

人工智能趋势——2023 年综述

随着DALLE 2 于 2022 年 4 月的宣布&#xff0c;关于2022 年初第三个 AI 冬天——或 AI 撞墙——的预言过时得很快而且效果不佳&#xff0c;随后出现了更多主要由扩散模型驱动的文本到图像应用程序&#xff0c;这是一个非常多产的领域用于计算机视觉研究及其他领域。AI 的 2022…

SpringCloud五大核心组件

Consul 等&#xff0c;提供了搭建分布式系统及微服务常用的工具&#xff0c;如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性token、全局锁、选主、分布式会话和集群状态等&#xff0c;满足了构建微服务所需的所有解决方案。 服务发现——Netflix Eureka …

六百亿流量、25万出场费,袁树雄身价是《汉川》杨语莲125倍

在文章开始之前&#xff0c;咱们不妨先看两组数据&#xff0c;第一组数据是六百个亿&#xff0c;第二组数据则是二十五万。乍一看并没有什么特别&#xff0c;但是要把这两组数据&#xff0c;和一名音乐人联系起来的话&#xff0c;就会产生非常神奇的效果。 《早安隆回》的创作者…

[oeasy]python0070_ 字体样式_下划线_中划线_闪动效果_反相_取消效果

字体样式 回忆上次内容 m 可以改变字体样式 0-10 之间设置的都是字体效果 0 复原1 变亮2 变暗 从3到10 又是什么效果 呢&#xff1f;&#xff1f;真的可以blink闪烁吗&#xff1f;&#x1f441; 3m 3m 实现斜体字的效果 4m 4m 对应着下划线 控制范围 通过控制字符串的位置…

DNS服务解析与原理笔记

引言DNS介绍DNS原理与解析DNS查询DNS服务搭建DNS劫持和污染计算机面试 or 真题DNS应用扩展参考与推荐引言 我想很多人遇到过这样一种情况&#xff0c;电脑突然上不了网了&#xff0c;或者说可以登陆QQ&#xff0c;但是进不了网页&#xff0c;任何网页都会出现如下的类似截图&a…

Android.mk文件编写

来源&#xff1a;https://www.jianshu.com/p/9aab51f4cd6f 1. Android.mk 介绍 Android.mk 是Android 提供的一种makefile 文件&#xff0c;注意用来编译生成 exe(二进制可执行文件)、so(动态库文件)、a(静态库文件)、jar(jar包) 和 apk 等文件。Android.mk和一个或多个.c的源…

PWN入门程序装载与虚拟内存 小白笔记

1.源代码到执行源代码test.c从磁盘中&#xff0c;通过gcc编译成a.out&#xff08;可执行文件&#xff09;。执行的时候&#xff0c;是将磁盘中的可执行文件&#xff08;a.out&#xff09;映像到内存中。2.节视图和段视图节视图&#xff08;磁盘中的可执行文件&#xff09;不同的…

采用消息中间件实现最终一致性的分布式事务

基于可靠消息服务的方案是通过消息中间件保证上下游应用数据操作的一致性。假设有A和B两个系统&#xff0c;分别可以处理任务A和任务B。此时存在一个业务流程&#xff0c;需要将任务A和任务B在同一个事务中处理。就可以使用消息中间件来实现这种分布式事务。 第一步&#xff…

一篇文章带你了解KendoReact DateRangePicker,让日期选择变得更酷炫!

Kendo UI致力于新的开发&#xff0c;来满足不断变化的需求。现在我们非常自豪地宣布&#xff0c;通过React框架的Kendo UI JavaScript封装来支持React Javascript框架。KendoReact能够为客户提供更好的用户体验&#xff0c;并且能够更快地构建更好的应用程序。理想情况下&#…