JUC在JDK1.8版本中引入了Striped64类及其四个实现类LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator,在原有的AtomicXXXXX等系列上,针对多线程并发情况进行了优化。

  Striped64类被设计用来支持累加器的的并发组件,可以在多线程高并发环境下安全的累加计数。Striped64内部存储了base、cells数组和cellsBusy锁,计数线程首先会尝试使用CAS对base变量进行更改,若更新成功,则计数完成,此时累加器竞争并不激烈。若未更新成功,表示此时竞争较激烈,需要引入cells数组,将待加数值放入cells内。调用累加器的线程与cells数组中的cell一一对应,各线程只操作自身对应的cell,Striped64根据线程的threadLocalRandomProbe属性值计算哈希值,将线程定位到某个固定的cell上。最后,需要统计计数结果时,若nullcells,则直接返回base值,若nullcells时,则累加cells各索引位置的值后返回。

  源码分析:

  实现基础:

// cell数组,若不为null,则为2的n次方.
transient volatile Cell[] cells;
// 基础值,在更新操作时基于CAS无锁技术实现原子更新.
transient volatile long base;
// CAS锁,用于保护创建或者扩展Cell表.
transient volatile int cellsBusy;

  基本方法:

// 依赖CAS,更新base的值.
final boolean casBase(long cmp, long val);
// 依赖CAS,更新cells的值.0表示未被占用,1表示已被占用.
final boolean casCellsBusy();
// 获取线程的threadLocalRandomProbe.
static final int getProbe();
// 被用来进行创建、初始化、扩展.针对long类型.
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended);
// 被用来进行创建、初始化、扩展.针对double类型.
final void doubleAccumulate(double x, DoubleBinaryOperator fn, boolean wasUncontended);

 longAccumulate和doubleAccumulate实现思路很类似,我们主要针对longAccumulate来逐步解析一下:

final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {int h;// 获取线程的threadLocalRandomProbe.// 若线程的threadLocalRandomProbe == 0,则重新初始化.if ((h = getProbe()) == 0) {// 强制初始化.ThreadLocalRandom.current(); h = getProbe();wasUncontended = true;}boolean collide = false;                // 无限循环.for (;;) {Cell[] as; Cell a; int n; long v;// as = cells; as != null && n = as.length; n > 0// cells 不为空时.if ((as = cells) != null && (n = as.length) > 0) {// (n - 1) & h 计算出当前线程在cells中的索引值.// 当cells[(n - 1)&h]不为null时.if ((a = as[(n - 1) & h]) == null) {// cellsBusy未被占用时.if (cellsBusy == 0) {// 新建cell.Cell r = new Cell(x);// cellsBusy未被占用同时更新cellsBusy=1.if (cellsBusy == 0 && casCellsBusy()) {// 创建标识.boolean created = false;try {               // Recheck under lockCell[] rs; int m, j;// rs = cells; rs != null && m = rs.length; m > 0// && j = (m - 1)&h; rs[j] == nullif ((rs = cells) != null &&(m = rs.length) > 0 &&rs[j = (m - 1) & h] == null) {// 将新建的Cell放入单元格.rs[j] = r;// 设置创建标识.created = true;}} finally {// 释放cellsBusy.cellsBusy = 0;}if (created)break;continue; }}collide = false;}// CAS已经失败.需要重新计算索引位置.else if (!wasUncontended)wasUncontended = true;// v = a.value// fn == null) ? v + x : fn.applyAsLong(v, x))// a.cas(v, fn)else if (a.cas(v = a.value, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;// cells最大长度为cpu的数量,cells != as表示cells已经被更新了.else if (n >= NCPU || cells != as)collide = false;else if (!collide)collide = true;// cellsBusy未被占用同时更新cellsBusy=1.else if (cellsBusy == 0 && casCellsBusy()) {try {// 扩展cells.if (cells == as) {// 新建原cells长度*2的新cells.Cell[] rs = new Cell[n << 1];// 原cells数据迁移至新cells.for (int i = 0; i < n; ++i)rs[i] = as[i];// 整理后的新cells生效.cells = rs;}} finally {cellsBusy = 0;}collide = false;continue; }// hash过程可能存在冲突,使用此方法可解决问题.h = advanceProbe(h);}// cellsBusy == 0 && cells == as(as在上面if语句里已经付过钱了).else if (cellsBusy == 0 && cells == as && casCellsBusy()) {boolean init = false;try {                          // if (cells == as) {// 默认初始化为2.Cell[] rs = new Cell[2];rs[h & 1] = new Cell(x);cells = rs;init = true;}} finally {cellsBusy = 0;}if (init)break;}// v = base.// (fn == null) ? v + x : fn.applyAsLong(v, x)).// casBase(v, fn);else if (casBase(v = base, ((fn == null) ? v + x :fn.applyAsLong(v, x))))break;                          }
}

  doubleAccumulate和longAccumulate,差别只在于数据类型的不同,其他逻辑是没有差别的,大家可以自己看一下这部分的源码。

  Striped64是个抽象类,Striped64为它的继承者们LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator提供了实现基础,下一篇,我们讲述LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator是使用方法和源码分析。

  注:文中源码均来自于JDK1.8版本,不同版本间可能存在差异。

​  如果有哪里有不明白或不清楚的内容,欢迎留言哦!

Striped64 深入源码解析相关推荐

  1. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  2. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  3. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  4. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  5. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  6. libev源码解析——定时器监视器和组织形式

    我们先看下定时器监视器的数据结构.(转载请指明出于breaksoftware的csdn博客) /* invoked after a specific time, repeatable (based o ...

  7. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  8. libev源码解析——I/O模型

    在<libev源码解析--总览>一文中,我们介绍过,libev是一个基于事件的循环库.本文将介绍其和事件及循环之间的关系.(转载请指明出于breaksoftware的csdn博客) 目前i ...

  9. libev源码解析——调度策略

    在<libev源码解析--监视器(watcher)结构和组织形式>中介绍过,监视器分为[2,-2]区间5个等级的优先级.等级为2的监视器最高优,然后依次递减.不区分监视器类型和关联的文件描 ...

最新文章

  1. C指针9:指针数组(即数组中的每个元素都是指针)
  2. 深度分析typedef--定义自己的数据类型
  3. 如何设计一个深度学习项目
  4. oracle preparedstatement,【JDBC】java PreparedStatement操作oracle数据库
  5. jenkins配置以cygwin环境的子节点
  6. SAP云平台CloudFoundry环境下的环境变量使用
  7. 分支-08. 高速公路超速处罚
  8. C\C++中声明与定义的区别
  9. Windows中安装Electron说明
  10. 深度XP完美精简版 (适合低配置电脑)——迅雷
  11. 他们都来全球边缘计算大会了,就差你了!
  12. 使用webots对并联结构进行仿真
  13. cac会议投稿难度大吗_英文(会议)论文投稿准备与流程
  14. 自己写了一个小小的双色球号码随机生成器
  15. 三角函数:加减法公式
  16. 二维数组malloc申请空间以及初始化方式
  17. Personalized Re-ranking for Recommendation
  18. 09组团队项目-Beta冲刺-1/5
  19. onsubmit和submit事件处理函数怎么不生效呢?
  20. cv2 python 获取斑马线_opencv 斑马线,条纹检测

热门文章

  1. C语言求最长公共子序列
  2. 微信小程序提现到零钱
  3. Typora也能做思维导图?做笔记的又一个绝佳选择?
  4. 数据可视化——坐标轴的定制
  5. 力扣(LeetCode)236. 二叉树的最近公共祖先(C语言)
  6. R语言survival包的survfit函数拟合生存曲线数据、survminer包的ggsurvplot函数可视化生存曲线、size参数指定曲线粗细、palette参数自定义生存曲线的颜色
  7. https://start.spring.io‘ 的初始化失败请检查 URL、网络和代理设置
  8. 解决鼠标右键特别慢的方法
  9. 问题解决:系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID=135170 中的about_Execution_Policies
  10. 桌面html文件图标异常,.htm.html文件图标无法显示的解决办法