java版本11.0.1,感觉写得太水了,等心情好的时候再重新编辑一下。

LongAdder中的核心逻辑主要由java.util.concurrent.atomic.Striped64维护,作为Striped64的继承类LongAdder定义了(LongAccumulator、DoubleAdder、DoubleAccumulator...)一些外围逻辑

    /**
* Cell(单元)表,不为null时大小为2的幂
*/
transient volatile Cell[] cells; /**
* 基值,主要用在没有竞争访问时使用, 也会用在竞争创建cells失败时的备选方案。CAS更新
*/
transient volatile long base; /**
* cells的自旋锁,在创建/扩容cells时会用到
*/
transient volatile int cellsBusy;

sum()遍历cells累加和base,reset()遍历cells和base赋值0,sumThenReset()遍历cells和base用CAS操作累加并赋值0。比较简单就这么概括一下。

主要方法LongAdder#add

  public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
/**
* 当一开始没有竞争调用时,CAS操作base值累加.当开始出现竞争时开始走下面的逻辑,不再累加base
*/
if ((cs = cells) != null || !casBase(b = base, b + x)) {
/**
* 设置“无竞争”标识
*/
boolean uncontended = true;
/**
* 当cells未初始化时继续以下判断逻辑,否则调用longAccumulate()
*/
if (cs == null || (m = cs.length - 1) < 0 ||
/**
* 根据访问线程的特征值(probeValue)获取cells中访问线程对应的cell. cell未初始化时调用longAccumulate()
* 线程特征值和cells.length的&操作确保cs[getProbe()&m]不会越界
*/
(c = cs[getProbe() & m]) == null ||
/**
* 对访问线程对应的cell的值CAS操作,并把执行结果赋值uncontended.若CAS操作失败则调用longAccumulate()
*/
!(uncontended = c.cas(v = c.value, v + x))) { longAccumulate(x, null, uncontended);
}
}
}

核心逻辑藏在了Striped64#longAccumulate,稍微花时间瞄了两眼。

  /**
* 处理涉及到初始化、扩容、创建及碰撞更新cell的情况.
*
* @param x 运算值
* @param fn 运算操作,null时为加法
* @param wasUncontended 调用该方法前CAS操作的结果,CAS操作失败则为false
*/
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
/**
* 线程特征值
*/
int h;
/**
* 当调用线程是第一次调用longAccumulate()时,赋值线程的特征值
*/
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current();
h = getProbe();
wasUncontended = true;
}
/**
* 表示对cell的获取是否与其他线程碰撞, 用来判断cells是否需要扩容
*/
boolean collide = false;
/**
* 未获取到cellsBusy时则自旋
*/
done: for (;;) {
Striped64.Cell[] cs; Striped64.Cell c; int n; long v;
/**
* 当cells已存在并且不为空时
*/
if ((cs = cells) != null && (n = cs.length) > 0) {
/**
* 当访问线程对应的cell尚未存在时,新增Cell(x)
*/
if ((c = cs[(n - 1) & h]) == null) {
/**
* 尝试获取自旋锁
*/
if (cellsBusy == 0) {
/**
* (乐观)创建Cell
*/
Striped64.Cell r = new Striped64.Cell(x);
/**
* 尝试获取自旋锁
*/
if (cellsBusy == 0 && casCellsBusy()) {
try {
Striped64.Cell[] rs; int m, j;
/**
* 在持有cellsBusy锁的情况下再次检查访问线程对应的cell是否已存在
*/
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
/**
* 新增Cell并且跳出自旋
*/
rs[j] = r;
break done;
}
} finally {
cellsBusy = 0;
}
/**
* 新增Cell失败,自旋
*/
continue;
}
}
/**
* 如果尝试获取自旋锁失败,说明已有其他线程占用了该Cell,
* 之后为减少碰撞会调用advanceProbe()
*/
collide = false;
}
/**
* 如果先前的cs[getProbe()&m]的CAS累加操作失败, 则wasUncontended赋值true
*/
else if (!wasUncontended)
wasUncontended = true;
/**
* 对cell执行CAS操作,成功则方法结束,失败则自旋
*/
else if (c.cas(v = c.value,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break;
/**
* 当cells大小大于逻辑cpu数,不扩容
*/
else if (n >= NCPU || cells != cs)
collide = false;
/**
* 通过collide是否碰撞判断是否执行下面的扩容逻辑
* collide==false时则自旋
*/
else if (!collide)
collide = true;
/**
* 执行到这里说明需要扩容
* 尝试获取cellsBusy锁扩容
*/
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs) // Expand table unless stale
cells = Arrays.copyOf(cs, n << 1);
} finally {
cellsBusy = 0;
}
collide = false;
continue;
}
/**
* 这是为了重新计算访问线程的特征值(advanceProbe(h))后自旋,减少碰撞
*/
h = advanceProbe(h);
}
/**
* 当cells没有正在初始化/扩容(cellsBusy == 0)并且cells未被创建(cells == cs)时,则设置cells的自旋锁cellBusy,开始创建cells对象
*/
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try {
/**
* 初始化cells和访问线程对应的Cell对象
*/
if (cells == cs) {
Striped64.Cell[] rs = new Striped64.Cell[2];
rs[h & 1] = new Striped64.Cell(x);
cells = rs;
break done;
}
} finally {
/**
* cells创建完成后释放cellBusy锁
*/
cellsBusy = 0;
}
}
/**
* 以上判断条件失败,则走备选逻辑:CAS操作运算base值(计算sum时会加上base值)
*/
else if (casBase(v = base,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break done;
}
}

懒得画流程图了~

瞄一眼LongAdder(jdk11)的更多相关文章

  1. 瞄一眼CopyOnWriteArrayList(jdk11)

    CopyOnWriteArrayList是ArrayList线程安全的变体.使用写时复制策略进行修改操作. 与之前版本较明显的区别是,jdk11中用来保护所有设值方法(mutator)的Reentra ...

  2. 瞄一眼,带你走进SparkSQL的世界

    本文由  网易云发布. 作者:范欣欣(本篇文章仅限知乎内部分享,如需转载,请取得作者同意授权.) 最近想来,大数据相关技术与传统型数据库技术很多都是相互融合.互相借鉴的.传统型数据库强势在于其久经考验 ...

  3. Java并发编程实战笔记

    如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误.有三种方式可以修复这个问题: i.不在线程之间共享该状态变量 ii.将状态变量修改为不可变的变量 iii.在访问状态变 ...

  4. 后HTML5时代

    十二年前,无论多么复杂的布局,在我们神奇的table面前,都不是问题:十年前,阿捷的一本<网站重构>,为我们开启了新的篇章:八年前,我们研究yahoo.com,惊叹它在IE5下都表现得如此 ...

  5. [深入JUnit] 测试运行的入口

    阅读前提 了解JUnit 对JUnit的内部实现有兴趣 不妨看看[深入JUnit] @Before, @After, @Test的秘密] 代码版本: junit 4.12代码搜索工具: http:// ...

  6. Intellij IDEA 一些不为人知的技巧

    Intellij IDEA 一些不为人知的技巧 2016/12/06 | 分类: 基础技术 | 0 条评论 | 标签: IntelliJ 分享到:38 原文出处: khotyn 今天又听了 Jetbr ...

  7. jq绑定事件的4种方式

    jQuery提供了多种绑定事件的方式,每种方式各有其特点,明白了它们之间的异同点,有助于我们在写代码的时候进行正确的选择,从而写出优雅而容易维护的代码.下面我们来看下jQuery中绑定事件的方式都有哪 ...

  8. [No00007D]2016-面经[上]

    面试常见问题: 题一:"请你自我介绍一下" 思路:1.这是面试的必考题目.2.介绍内容要与个人简历相一致.3.表述方式上尽量口语化.4.要切中要害,不谈无关.无用的内容.5.条理要 ...

  9. [No00006D]下载离线版的github for windows【以Github for Windows 3.0.110.为例】

    目录 先上地址后讲原理: 原理: 11个目录的文件怎么一口气下载呢? 最后,把下好的文件批量名,同时将GitHub.exe.manifest也放到软件根目录下(与GitHub.exe同级): 今后的猜 ...

随机推荐

  1. 【转】JavaScript 节点操作 以及DOMDocument属性和方法

    最近发现DOMDocument对象很重要,还有XMLHTTP也很重要 注意大小写一定不能弄错. 属性: 1Attributes 存储节点的属性列表(只读) 2childNodes 存储节点的子节点列表 ...

  2. maven,gradle本地缓存位置

    gradle: 配置系统环境变量GRADLE_USER_HOME即可,值为缓存位置. maven: 修改settings文件:maven的home路径下的conf文件夹下的settings.xml 对 ...

  3. cocos2dx lua 打印和保存日志

    在2d游戏中,经常会出现闪退或者报错的问题,通过写文本,将日志文件发送给服务端,让后端人员进行分析. 通过lua打印日志在文本文件中: local file = io.open(cc.FileUtil ...

  4. DC 课程内容

  5. lavarel 添加自定义辅助函数

    Laravel 提供了很多 辅助函数,有时候我们也需要创建自己的辅助函数. 必须 把所有的『自定义辅助函数』存放于 bootstrap 文件夹中. 并在 bootstrap/app.php 文件的最顶 ...

  6. 运用Python制作你心目中的完美女神脸!

    简介 写这个项目的本来目的是通过构建一个神经网络来训练人脸图片,最后达到能根据图片自动判断美丑的效果.可能是因为数据集过小,或者自己参数一直没有调正确,无论我用人脸关键点训练还是卷积神经网络训练,最后 ...

  7. Ubuntu 15.04 安装配置 Qt + SQLite3

    序 最近需要在Ubuntu下使用Qt开发项目,选择简单小巧的SQLite数据库,现将安装配置以及简单操作记录如下,以便日后查阅. 安装Qt CMake和Qt Creator是Linux下开发C++程序 ...

  8. LeetCode(155) Min Stack

    题目 Design a stack that supports push, pop, top, and retrieving the minimum element in constant time. ...

  9. Ubuntu关机与重启的相关指令

    将数据同步写入到磁盘中的指令:sync 惯用的关机指令:shutdown 重新启动,关机:reboot,halt,poweroff shutdown可完成如下工作: 1.可以自由选择关机模式:是要关机 ...

  10. 00018_流程控制语句switch

    1.选择结构switch switch 条件语句也是一种很常用的选择语句,它和if条件语句不同,它只能针对某个表达式的值作出判断,从而决定程序执行哪一段代码. 2.switch语句的语法格式 swit ...