Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >AtomicLong和LongAdder的区别

AtomicLong和LongAdder的区别

作者头像
用户1212940
发布于 2022-04-13 05:31:07
发布于 2022-04-13 05:31:07
42600
代码可运行
举报
文章被收录于专栏:LambdaLambda
运行总次数:0
代码可运行

AtomicLong和LongAdder的区别

前言

最近在看到不少框架里面使用到了LongAdder这个类,而并非AtomicLong,很是困惑,于是专门看了LongAdder的源码,总结一下这两个的区别。

AtomicLong原理

就像我们所知道的那样,AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。那么LongAdder又是使用到了什么原理?难道有比cas更加快速的方式?

LongAdder原理

首先我们来看一下LongAdder有哪些方法?

可以看到和AtomicLong基本类似,同样有增加、减少等操作,那么如何实现原子的增加呢?

这里写图片描述

我们可以看到一个Cell的类,那这个类是用来干什么的呢?

我们可以看到Cell类的内部是一个volatile的变量,然后更改这个变量唯一的方式通过cas。我们可以猜测到LongAdder的高明之处可能在于将之前单个节点的并发分散到各个节点的,这样从而提高在高并发时候的效率。

下面我们来验证我们的观点,我们接着看上图的add方法,如果cell数组不为空,那么就尝试更新base元素,如果更新成功,那么就直接返回。base元素在这里起到了一个什么作用呢?可以保障的是在低并发的时候和AtomicLong一样的直接对基础元素进行更新。   而如果cell为空或者更新base失败,我们看接下来的那个if判断,即如果as不为空并且成功更新对应节点的数据,则返回,否则就会进入longAccumulate()方法。   图有点大,无法截图,直接贴源码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
        for (;;) {
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
                if ((a = as[(n - 1) & h]) == null) { //如果对应位置没有数据,那么直接插入元素
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                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)     //标示冲突标志位 ,进行重试 CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale,如果已经cell数组的大小已经超过了CPU核数,那么再扩容没意义了,直接重试,或者有别的线程扩容导致变更了数组,设置标示位,进行重试,,避免一失败就扩容
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {//开始扩容
                    try {
                        if (cells == as) {      // Expand table unless stale
                            Cell[] rs = new Cell[n << 1];
                            for (int i = 0; i < n; ++i)
                                rs[i] = as[i];
                            cells = rs;
                        }
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h); //扩容完成以后,重新初始化要更新的索引值,尽量保障可以更新成功
            }
            else if (cellsBusy == 0 && cells == as && //初始化casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }

上面的代码主要有三个分支:     1. 如果数组不为空     2. 数据为空,则初始化     3. 前面都更新失败了,尝试更新base数据    cellBusy是一个标示元素,只有当修改cell数组大小或者插入元素的时候才会修改。分支二、分支三都很简单,我们来重点分析一下分支一。    当要更新的位置没有元素的时候,首先cas标志位,防止扩容以及插入元素,然后插入数据。如果成功直接返回,否则标示发生了冲突,然后重试。如果对应的位置有元素则更新,如果更新失败,进行判断是否数组的大小已经超过了cpu的核数,如果大于的话,则意味着扩容没有意义。直接重试。否则进行扩容,扩容完成后,重新设置要更新的位置,尽可能保证下一次更新成功。   我们来看一下如何统计计数。

当计数的时候,将base和各个cell元素里面的值进行叠加,从而得到计算总数的目的。这里的问题是在计数的同时如果修改cell元素,有可能导致计数的结果不准确。

总结:

LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。 缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/06/06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
LongAdder 源码分析
按照作者的说法,LongAdder 在多个线程更新下比 AtomicLong 性能更好,但要消耗更多的空间
itliusir
2020/02/10
5640
比AtomicLong更优秀的LongAdder确定不来了解一下吗?
文章中所有高清无码图片在公众号号回复: 图片666 即可查阅, 可直接关注公众号:壹枝花算不算浪漫
一枝花算不算浪漫
2020/05/15
1.1K0
六个面试题层层剖析——LongAddr原子类 顶
可以看到,Cell的构造很简单,其内部维护一个被声明为Volatile的变量,保证了内存的可见性。
须臾之余
2019/07/18
9390
六个面试题层层剖析——LongAddr原子类
                                                                            顶
LongAdder(下)代码实现原理
为了解决高并发下多线程对一个变量CAS争夺失败后进行自旋而造成的降低并发性能的问题,LongAdder在内部维护多个Cell元素**(一个动态的Cell数组)**来分担单个变量进行争夺开销。下面围绕以下话题从源码角度来分析LongAdder的实现!
YanL
2020/04/29
5260
LongAdder(下)代码实现原理
Java8 原子弹类之LongAdder源码分析add使用场景 LongAdder是否能够替换AtomicLong
简单来说,这个类用于在多线程情况下的求和。 官方文档的说明 从关键方法 add 包含了一个Cell数组,Striped64的一个内部类 Padded variant of Atomic
JavaEdge
2018/05/16
1.5K0
JDK 8 新增的 LongAdder,得过来看一下!
" 在介绍 AtomicInteger 时,已经说明在高并发下大量线程去竞争更新同一个原子变量时,因为只有一个线程能够更新成功,其他的线程在竞争失败后,只能一直循环,不断的进行 CAS 尝试,从而浪费了 CPU 资源。而在 JDK 8 中新增了 LongAdder 用来解决高并发下变量的原子操作。下面同样通过阅读源码来了解 LongAdder 。 "
程序员小航
2020/11/23
3940
JDK 8 新增的 LongAdder,得过来看一下!
LongAdder解析
 对LongAdder的最初了解是从Coolshell上的一篇文章中获得的,但是一直都没有深入的了解过其实现,只知道它相较于AtomicLong来说,更加适合写多读少的并发情景。今天,我们就研究一下LongAdder的原理,探究一下它如此高效的原因。
程序员历小冰
2019/01/24
4640
LongAdder解析
LongAdder的源码学习与理解
因为CPU与内存之间速度还是存在较大差距所以现在计算机在内存与CPU之间引入了三级缓存
才疏学浅的木子
2022/11/13
2400
LongAdder的源码学习与理解
Java8原子弹类之LongAdder源码分析
JDK 8开始,针对Long型的原子操作, Java又提供了LongAdder. LongAccumulator; 针对Double类型,Java提供了DoubleAdder、DoubleAccumulator。
JavaEdge
2022/01/25
3400
Java8原子弹类之LongAdder源码分析
《从Java面试题看源码》-LongAdder、LongAccumulator是个什么东西?
LongAdder 继承自Striped64,并实现了Serializable序列化接口。
阿提说说
2022/12/02
6710
阿里巴巴写进Java开发手册里推荐的JUC工具类:LongAdder,确定不点进来学一下嘛?
昨天又从朋友那里倒腾过来一个好的题材:“JUC中的高性能计数器工具类LongAdder”。初步在网上搜索之后,发现其实阿里巴巴的Java开发手册中也有对于这个工具包的推荐:
程序员牛肉
2025/02/04
920
阿里巴巴写进Java开发手册里推荐的JUC工具类:LongAdder,确定不点进来学一下嘛?
LongAdder源码【原创+图解+视频讲解】
【总结者】LongAdder源码讲解(图解+代码逐行分析)4K面试必看_哔哩哔哩_bilibili
编程张无忌
2023/01/06
3770
LongAdder源码【原创+图解+视频讲解】
高性能原子类
公共父类Striped64是实现中的核心,它实现一些核心操作,处理64位数据,很容易就能转化为其他基本类型,是个通用的类。二元算术运算,指的是你可以给它提供一个二元算术方式,这个类按照你提供的方式进行算术计算,并保存计算结果。二元运算中第一个操作数是累积器中某个计数单元当前的值,另外一个值是外部提供的。
黑洞代码
2021/01/14
6400
高性能原子类
Java Review - 并发编程_JDK 8新增的原子操作类LongAdder & LongAccumulator
Java Review - 并发编程_原子操作类原理剖析中提到了 AtomicLong通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说它的性能已经很好了,但是JDK开发组并不满足于此。使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS操作会成功,这就造成了大量线程竞争失败后,会通过无限循环不断进行自旋尝试CAS的操作,而这会白白浪费CPU资源。
小小工匠
2021/12/01
2710
Java Review - 并发编程_JDK 8新增的原子操作类LongAdder & LongAccumulator
LongAdder源码分析
并发环境重计数 AtomicLong 的 Add 操作是依赖自旋不断的 CAS 去累加一个 Long 值。如果在竞争激烈的情况下,CAS 操作不断的失败,就会有大量的线程不断的自旋尝试 CAS 会造成 CPU 的极大的消耗。
leobhao
2022/06/28
2900
LongAdder源码分析
(转)比AtomicLong还高效的LongAdder源码解析
接触到AtomicLong的原因是在看guava的LoadingCache相关代码时,关于LoadingCache,其实思路也非常简单清晰:用模板模式解决了缓存不命中时获取数据的逻辑,这个思路我早前也正好在项目中使用到。 言归正传,为什么说LongAdder引起了我的注意,原因有二:
屈定
2018/09/27
8090
(转)比AtomicLong还高效的LongAdder源码解析
不同并发场景下LongAdder与AtomicLong如何选择
本篇文章并不会直接进入主题讲为什么LongAdder性能好于AtomicLong,而是先介绍一下volatile,一是可以将最近所学理一下,二是我觉得AtomicLong是为了解决volatile不适用的场景,就当是一个铺垫,然后在介绍AtomicLong,最后在介绍LongAdder以及LongAdder和AtomicLong的性能比较 ,如果直接想看原因直接跳转至文末:产生性能差异的原因。
架构师修炼
2021/11/19
5480
不同并发场景下LongAdder与AtomicLong如何选择
一篇文章快速搞懂 Atomic(原子整数/原子引用/原子数组/LongAdder)
相信大部分开发人员,或多或少都看过或写过并发编程的代码。并发关键字除了Synchronized(如有不懂请移至传送门,[万字长文,建议收藏]关于Synchronized锁升级,你该了解这些  ),还有另一大分支Atomic。如果大家没听过没用过先看基础篇,如果听过用过,请滑至底部看进阶篇,深入源码分析。
陈琛
2020/06/17
1.3K0
一篇文章快速搞懂 Atomic(原子整数/原子引用/原子数组/LongAdder)
20.Atmoic系列Strimped64分段锁底层实现源码剖析
老王:小陈啊,上一章节我们对LongAdder的底层源码、实现机制进行了深入了剖析,包括AtomicInteger在高并发竞争下导致的大量自旋的问题,以及LongAdder是怎么使用分段锁优化这个问题的。
终有救赎
2023/10/16
2540
20.Atmoic系列Strimped64分段锁底层实现源码剖析
死磕 java并发包之LongAdder源码分析
LongAdder是java8中新增的原子类,在多线程环境中,它比AtomicLong性能要高出不少,特别是写多的场景。
彤哥
2019/07/08
3930
死磕 java并发包之LongAdder源码分析
推荐阅读
相关推荐
LongAdder 源码分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验