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

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. 名校算法博士找工作两月无果,因本科是不知名学校被婉拒
  2. 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)...
  3. 为什么加了@Transactional注解,事务没有回滚?
  4. 常用chrome插件
  5. Windows下编程需要看哪些书
  6. Train Problem I(模拟栈)
  7. 推荐一个高质量的git命名查询和学习的github仓库git-recipes
  8. carplant_mxnet
  9. ejabberd源码学习——方法注册模式
  10. 控制台接收信息转发_微信语音信息无法转发?原来这才是正确的转发姿势,看完涨知识了...
  11. ActiveMQ笔记(二)
  12. 肖哥所有课程/HCNA HCNP/安全/云计算/虚拟化/linux/视频教程/资料软件下载链接
  13. U盘被写保护,无法格式化
  14. 掌握旋风:SAP成功实施实地指南
  15. 【TypeScript】使用CRA创建支持TS的React项目(从踩坑到放弃)
  16. web 服务器有哪些
  17. 适合python的vim设置
  18. nginx 正向代理http和https
  19. 【若依】开源框架学习笔记 07 - 登录认证流程(Spring Security 源码)
  20. 第四届“图灵杯”NEUQ-ACM程序设计人赛真题重现

热门文章

  1. 手机浏览器UA测试(三)
  2. JAVA程序包装成桌面应用程序
  3. 转行面试,跳槽面试,软件测试人员都必须知道的这几种面试技巧
  4. 常见的软件测试人员面试问题
  5. 4G模块发送短信流程
  6. 鲍春健:从“走进客户”奔向“成为客户”
  7. 性能工具之Java分析工具BTrace入门
  8. linux终端下如何分屏,ubuntu terminal 终端分屏
  9. 部署开源软件snipe-it
  10. 微信AirSync服务之计步器