瞄一眼LongAdder(jdk11)
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)的更多相关文章
- 瞄一眼CopyOnWriteArrayList(jdk11)
CopyOnWriteArrayList是ArrayList线程安全的变体.使用写时复制策略进行修改操作. 与之前版本较明显的区别是,jdk11中用来保护所有设值方法(mutator)的Reentra ...
- 瞄一眼,带你走进SparkSQL的世界
本文由 网易云发布. 作者:范欣欣(本篇文章仅限知乎内部分享,如需转载,请取得作者同意授权.) 最近想来,大数据相关技术与传统型数据库技术很多都是相互融合.互相借鉴的.传统型数据库强势在于其久经考验 ...
- Java并发编程实战笔记
如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误.有三种方式可以修复这个问题: i.不在线程之间共享该状态变量 ii.将状态变量修改为不可变的变量 iii.在访问状态变 ...
- 后HTML5时代
十二年前,无论多么复杂的布局,在我们神奇的table面前,都不是问题:十年前,阿捷的一本<网站重构>,为我们开启了新的篇章:八年前,我们研究yahoo.com,惊叹它在IE5下都表现得如此 ...
- [深入JUnit] 测试运行的入口
阅读前提 了解JUnit 对JUnit的内部实现有兴趣 不妨看看[深入JUnit] @Before, @After, @Test的秘密] 代码版本: junit 4.12代码搜索工具: http:// ...
- Intellij IDEA 一些不为人知的技巧
Intellij IDEA 一些不为人知的技巧 2016/12/06 | 分类: 基础技术 | 0 条评论 | 标签: IntelliJ 分享到:38 原文出处: khotyn 今天又听了 Jetbr ...
- jq绑定事件的4种方式
jQuery提供了多种绑定事件的方式,每种方式各有其特点,明白了它们之间的异同点,有助于我们在写代码的时候进行正确的选择,从而写出优雅而容易维护的代码.下面我们来看下jQuery中绑定事件的方式都有哪 ...
- [No00007D]2016-面经[上]
面试常见问题: 题一:"请你自我介绍一下" 思路:1.这是面试的必考题目.2.介绍内容要与个人简历相一致.3.表述方式上尽量口语化.4.要切中要害,不谈无关.无用的内容.5.条理要 ...
- [No00006D]下载离线版的github for windows【以Github for Windows 3.0.110.为例】
目录 先上地址后讲原理: 原理: 11个目录的文件怎么一口气下载呢? 最后,把下好的文件批量名,同时将GitHub.exe.manifest也放到软件根目录下(与GitHub.exe同级): 今后的猜 ...
随机推荐
- 响应者链和Hit-Test 机制
概念: 响应者 : 对用户交互动作事件进行响应的对象.响应者链:成为处理事件的响应者的先后顺序链. 1.Hit-Test 机制 当用户触摸(Touch)屏幕进行交互时,系统首先要找到响应者(Respo ...
- UI调试神器 for ios:Reveal的使用与破解
aaarticlea/jpeg;base64,/9j/4AAQSkZJRgABAQEAkACQAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aH
- 03等待多个线程返回WaitForMultipleObject
二. WaitForMultipleObject 等待单个线程返回 1. 函数原型 DWORD WINAPI WaitForMultipleObjects( _In_ DWORD nCount, _I ...
- 【mysql】【转发】Cannot proceed because system tables used by Event Scheduler were found damaged at server start
本地:mac 10.12.3 mysql 5.6 远程:linux 7.3 mysql 5.7.18. (远程数据库yum安装,又5.6升级到5.7) 步骤:从本地数据库导出数据到远 ...
- Form和ModelForm组件
Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否 ...
- BFS、模拟:UVa1589/POJ4001/hdu4121-Xiangqi
Xiangqi Xiangqi is one of the most popular two-player board games in China. The game represents a ba ...
- Linux学习-Linux 的开机流程分析
开机流程一览 系统开机的经过可以汇整成底下的流程的: 加载 BIOS 的硬件信息与进行自我测试,并依据设定取得第一个可开机的装置; 读取并执行第一个开机装置内 MBR 的 boot Loader (亦 ...
- Markdown 使用锚点
首先是建立一个跳转的连接: [说明文字](#jump) 然后标记要跳转到什么位置即可: <span id = "jump">跳转到这里:</span>
- 剑指offer算法编程题目部分汇总(解法略)
总结一下本书中遇到的大部分面试题.面试题3:二维数组中的查找 题目:在一个二维数组中,每一行都按照从左到右的递增顺序排列,每一列都按照从上到下递增的顺序排列,请完成一个函数,输入这样的一个整数,判断数 ...
- MAC OS X 终端命令入门
在这里记下..防止丢失 pwd 当前工作目录 cd(不加参数) 进root cd(folder) 进入文件夹 cd .. 上级目录 cd ~ 返回root cd - 返回上一个访问的目录 rm 文件名 ...