Java 多线程学习(4)浅析 LongAdder、LongAccumulator 和 Striped64 的底层实现原理
转载请注明原创出处,谢谢!
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 的底层实现原理相关推荐
- Java多线程学习处理高并发问题
在程序的应用程序中,用户或请求的数量达到一定数量,并且无法避免并发请求.由于对接口的每次调用都必须在返回时终止,因此,如果接口的业务相对复杂,则可能会有多个用户.调用接口时,该用户将冻结. 以下内容将 ...
- java多线程学习-java.util.concurrent详解
http://janeky.iteye.com/category/124727 java多线程学习-java.util.concurrent详解(一) Latch/Barrier 博客分类: java ...
- Java多线程学习(二)synchronized关键字(1)
转载请备注地址: https://blog.csdn.net/qq_34337272/article/details/79655194 Java多线程学习(二)将分为两篇文章介绍synchronize ...
- java多线程学习笔记。
java多线程学习笔记 线程的优缺点: 多线程的好处: 充分利用多处理核心,提高资源的利用率和吞吐量. 提高接口的响应效率,异步系统工作. 线程的风险: 安全危险(竞争条件):什么坏事都没有发生.在没 ...
- 【转】Java 多线程学习
原网址:https://www.cnblogs.com/yjd_hycf_space/p/7526608.html Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实J ...
- 转:Java多线程学习(总结很详细!!!)
Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程 ...
- Java多线程学习——01
Java多线程学习--01 1.核心概念 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念 进程Process:是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单 ...
- java线程学习,GitHub - zksir/thread: Java多线程学习
Java多线程学习 threadcoreknowledge包----线程核心知识基础 createthreads包 创建线程 1.实现多线程的方法是1种还是2种还是4种? Oracle官方:2种,一种 ...
- Java多线程学习之路(四)---死锁(DeadLock)
Java多线程学习之路(四)-死锁(DeadLock) 1.定义 死锁就是多个线程在竞争共享资源的时候,相互阻塞,不能脱身的状态(个人理解).其实死锁一定程度上可以看成一个死循环. 举个现实生活中的例 ...
最新文章
- BrowserSync前端同步测试工具
- linux根目录挂载到2440开发板,飞凌2440开发板挂载NFS
- 一款b站视频下载工具软件mac版
- SAP CRM服务订单状态和SAP S/4生产订单状态
- 洛谷 P3243 【[HNOI2015]菜肴制作】
- Linux网络模块全局变量,()不是Linux系统的特色.
- 零基础入门stm32需要学这些东西
- mac打开chm文件_Mac无法打开TXT文件怎么办?Mac无法打开txt文档的解决方法
- poj3660 Cow Contest
- python怎么编程hello world用geany_第一个Hello World 程序
- 锐起无盘服务器缓存,锐起无盘v0412(集成虚拟盘+SSD缓存+R2开包版)
- [jbb0523整理]压缩感知中的数学知识:NP-hard问题
- shal+php,学习笔记---PHP中几种加密算法(MD5,shal,base64_encode等)
- Python3: 超简单个性动态二维码制作
- 为你的掘金和 GitHub 设定个性域名
- 企业运维实战--MFS分布式文件系统
- 服务器未能识别网络,服务器无法识别url中特殊字符的处理方式
- 二、Contiki移植
- 从正多面体到斐波拉契网格
- dellR730ch插拔sdka