转载请注明原创出处,谢谢!

HappyFeet的博客

LongAdder、LongAccumulator 和 Striped64,其实还有 DoubleAdder 和 DoubleAccumulator,这几个类是 j.u.c 包的作者 Doug Lea 在 JDK 1.8 版本的时候加进来的。

由于这几个类的实现几乎一模一样,所以这里仅以 LongAdder 为例,分析其底层实现原理。


1、LongAdder 和 AtomicLong

AtomicLong 是通过 CAS 来对一个 value 做原子更新操作。在竞争程度较低的时候,它的效率很高。但是在竞争激烈的情况下,CAS 很容易失败。

LongAdder 可以理解为加强版的 AtomicLong,但是又有一点点区别。

在竞争程度低的时候,LongAdder 和 AtomicLong 的特点是一样的。但是在竞争激烈的情况下,它的性能会优于 AtomicLong,不过它不能保证 sum 返回值的准确性(具体原因后面会讲到)。

下面我们来看看 LongAdder 为什么在竞争激烈的情形下性能要优于 AtomicLong。

2、LongAdder 的底层实现原理

transient volatile long base;
transient volatile Cell[] cells;

首先 LongAdder 维护了一个 base 值,这个值和 AtomicLong 中的 value 的作用一样;除此之外,它还维护了一个 Cell[] cells 数组,初始值为 null,只有在对 base 执行 CAS 更新失败时(说明竞争激烈)才会用到 cells 这个数组。

Cell 类很简单,里面就放了一个 value 和一个支持 CAS 更新 value 的 cas 方法。

并且如果看过 HashMap 底层实现的话,你会发现这个 cells 数组跟 HashMap 中的数组很像,特别像。它长度是 2 的 n 次幂,也是通过 h & (length - 1) 来获取数组的下标。

当 n 个线程同时执行 add 方法时,

  • 对于 AtomicLong 来说只会有一个线程会执行成功,剩下的都会失败进入自旋,最终看起来就像是串行的在执行。

  • 而对于 LongAdder,它会根据线程的 probe 值( Thread 类中的 threadLocalRandomProbe 字段)求得一个 cells 的数组下标,获取到 cell 值。n 个线程被分散到不同的 cell 中,在执行 CAS 的时候也就不会失败了。

    这里可以想一下如果出现哈希冲突,会是一个什么样的情况?

下面我们来看看 LongAdder 的核心实现逻辑:LongAdder 中的 add 方法和Striped64 中的 longAccumulate 方法。

(1)LongAdder 中的 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);}
}

流程图大致是这样子的:

(2)Striped64 中的 longAccumulate 方法:

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended)

longAccumulate 方法很多个逻辑分支,就不一一分析每个 if 代表什么含义了,这里主要讲一讲它的思路。

  • (1)首先,它是一个死循环,只有在 x 的值成功加到 base 或者 cells 中才会跳出循环;

  • (2)它通过 probe & (length - 1) 将多个线程分流,分散到不同的 cells 中,从而减小了 CAS 失败的概率;

  • (3)cells 会随着竞争程度的升高而扩容,当达到最大值(大于等于 CPU 的数量)后不再扩容。

    当超过 CPU 数量之后再扩容就没有意义了,可以思考一下为什么?

  • (4)通过 CAS 和一个 int 变量(cellsBusy)实现了一个自旋锁,在初始化和扩容 cells 的时候同步。

如果想知道 longAccumulate 方法每一个 if 具体做了啥,可以参考文末参考资料的第一个链接。

3、LongAdder 的特点及适用场景

从前面可以知道,LongAdder 把值加到了 base 和 cells 数组每个元素的 value 中,所以最终的结果应该是这些属性加起来。

我们来看它的 sum 方法实现:

public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}

sum 方法是返回当前总和,但这个返回值并不是一个原子快照。什么意思呢?

假如在 sum 的过程中,没有线程调用 add 方法,这种情况下返回的值是准确的;但在这个过程中,cell[0] 已经被加过了,这时恰好有一个线程调用了 add 方法,对 cell[0] 作了更新,但这个时候 sum 方法已经统计不到了,所以这种值不会被加进来。

所以它的特点总结起来就是:

  • 在高并发下有更好的性能表现;
  • 但高并发下不能保证 sum 返回值的准确性;

所以对于高并发、且对于统计值的精确性不是特别高的场景比较适合使用 LongAdder。

例如一些网站上的实时在线人数统计、实时弹幕条数这种。而如果对准确性有严格要求的话,就只能使用 AtomicLong 了。

4、一些思考

  • LongAdder 体现了用空间换时间的思想
  • 到底哪些情况下会用到 LongAdder 呢?(由于目前并没有在业务上遇到过需要使用 LongAdder 的情况,所以只能根据它的特点思考可能使用会使用 LongAdder 的场景。)

最后写给自己的话:勤能补拙,有付出才会有回报!手动给自己点个赞~ (*╹▽╹*)

参考资料:

(1)LongAdder类学习笔记

(2)深入剖析LongAdder是咋干活的

Java 多线程学习(4)浅析 LongAdder、LongAccumulator 和 Striped64 的底层实现原理相关推荐

  1. Java多线程学习处理高并发问题

    在程序的应用程序中,用户或请求的数量达到一定数量,并且无法避免并发请求.由于对接口的每次调用都必须在返回时终止,因此,如果接口的业务相对复杂,则可能会有多个用户.调用接口时,该用户将冻结. 以下内容将 ...

  2. java多线程学习-java.util.concurrent详解

    http://janeky.iteye.com/category/124727 java多线程学习-java.util.concurrent详解(一) Latch/Barrier 博客分类: java ...

  3. Java多线程学习(二)synchronized关键字(1)

    转载请备注地址: https://blog.csdn.net/qq_34337272/article/details/79655194 Java多线程学习(二)将分为两篇文章介绍synchronize ...

  4. java多线程学习笔记。

    java多线程学习笔记 线程的优缺点: 多线程的好处: 充分利用多处理核心,提高资源的利用率和吞吐量. 提高接口的响应效率,异步系统工作. 线程的风险: 安全危险(竞争条件):什么坏事都没有发生.在没 ...

  5. 【转】Java 多线程学习

    原网址:https://www.cnblogs.com/yjd_hycf_space/p/7526608.html Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实J ...

  6. 转:Java多线程学习(总结很详细!!!)

    Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程 ...

  7. Java多线程学习——01

    Java多线程学习--01 1.核心概念 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念 进程Process:是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单 ...

  8. java线程学习,GitHub - zksir/thread: Java多线程学习

    Java多线程学习 threadcoreknowledge包----线程核心知识基础 createthreads包 创建线程 1.实现多线程的方法是1种还是2种还是4种? Oracle官方:2种,一种 ...

  9. Java多线程学习之路(四)---死锁(DeadLock)

    Java多线程学习之路(四)-死锁(DeadLock) 1.定义 死锁就是多个线程在竞争共享资源的时候,相互阻塞,不能脱身的状态(个人理解).其实死锁一定程度上可以看成一个死循环. 举个现实生活中的例 ...

最新文章

  1. BrowserSync前端同步测试工具
  2. linux根目录挂载到2440开发板,飞凌2440开发板挂载NFS
  3. 一款b站视频下载工具软件mac版
  4. SAP CRM服务订单状态和SAP S/4生产订单状态
  5. 洛谷 P3243 【[HNOI2015]菜肴制作】
  6. Linux网络模块全局变量,()不是Linux系统的特色.
  7. 零基础入门stm32需要学这些东西
  8. mac打开chm文件_Mac无法打开TXT文件怎么办?Mac无法打开txt文档的解决方法
  9. poj3660 Cow Contest
  10. python怎么编程hello world用geany_第一个Hello World 程序
  11. 锐起无盘服务器缓存,锐起无盘v0412(集成虚拟盘+SSD缓存+R2开包版)
  12. [jbb0523整理]压缩感知中的数学知识:NP-hard问题
  13. shal+php,学习笔记---PHP中几种加密算法(MD5,shal,base64_encode等)
  14. Python3: 超简单个性动态二维码制作
  15. 为你的掘金和 GitHub 设定个性域名
  16. 企业运维实战--MFS分布式文件系统
  17. 服务器未能识别网络,服务器无法识别url中特殊字符的处理方式
  18. 二、Contiki移植
  19. 从正多面体到斐波拉契网格
  20. dellR730ch插拔sdka

热门文章

  1. #455 科技乱炖:看完《流浪地球2》,我们为行星发动机设计了网络架构
  2. 解决鼠标右键特别慢的方法
  3. 阿里大数据平台MaxCompute初窥
  4. C++数组连接求能被7整除的数
  5. 关于猫鼻支的防范和治疗
  6. linux pg启动日志查看,pg日志分析
  7. 腾讯位置服务教你轻松搞定微信发送位置功能
  8. 【ROM制作工具】如何三分钟学会制作手机线刷包?一键制作线刷包详细教程
  9. 如何跨入AI的大门 Python人工智能方向怎么样
  10. Control your destiny, or someone else will