LongAdder 源码分析
LongAdder
LongAdder 能解决什么问题?什么时候使用 LongAdder?
1)LongAdder 内部包含一个基础值【base】和一个单元【Cell】数组。
没有竞争的情况下,要累加的数会累加到这个基础值上;
如果有竞争的话,LongAdder 会将要累加的数累加到 cell 数组的某个单元里面。
所以整个 LongAdder 的值包括基础值和 Cell 数组中所有单元的值的总和。
2)在竞争不激烈时,其性能类似与 AtomicLong,但是需要更多的存储空间;
在竞争激烈时,其吞吐量要远高于 AtomicLong【以空间换时间】。
如何使用 LongAdder?
1)高并发场景下的多线程计数器
使用 LongAdder 有什么风险?
2)通过 sum 计算结果值时如果存在多线程写入,则其值可能是不精确的。
LongAdder 核心操作的实现原理?
创建实例
/**
* 创建一个累积和为 0 的 LongAdder 实例
*/
public LongAdder() {
}
累加值
/**
* 原子累加指定的值 x 到 LongAdder
*/
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
/**
* 1)如果 cells 为 null,则尝试原子更新值到 base 中
* 2)如果 cells 不为 null,则将其累加到其中一个 cell 中。
* if (!casBase(b = base, b + x)) {
* 首先尝试原子更新值到 base 中,更新失败则将其累加到指定的 cell 中?
* }
*/
if ((cs = cells) != null || !casBase(b = base, b + x)) {
/**
* 1)cells 为 null,并且原子更新 base 值失败,出现在第一次竞争发生时。
* 2)cells 不为 null
* cell 是否发生竞争的标记
*/
boolean uncontended = true;
/**
* cells 不为 null &&
* 其长度大于 1 &&
* 基于当前线程的探测值定位的 cell 不为 null &&
* 则尝试原子更新目标 cell 值
*/
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[Striped64.getProbe() & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x))) {
/**
* 1)cell 为 null
* 2)原子更新目标 cell 值失败,即单个 cell 发生竞争
*/
longAccumulate(x, null, uncontended);
}
}
}
Striped64#
/**
* 尝试原子更新 base 值
*/
final boolean casBase(long cmp, long val) {
return Striped64.BASE.compareAndSet(this, cmp, val);
}
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
int h;
// 探测值为 0
if ((h = Striped64.getProbe()) == 0) {
// 强制初始化当前线程的线程局部随机数
ThreadLocalRandom.current(); // force initialization
// 读取新的探测值
h = Striped64.getProbe();
// 发生 cell 竞争
wasUncontended = true;
}
boolean collide = false; // True if last slot nonempty
done: for (;;) {
Cell[] cs; Cell c; int n; long v;
// 1)cells 已经完成初始化
if ((cs = cells) != null && (n = cs.length) > 0) {
// 1-1)基于线程探测值定位的 cell 为 null
if ((c = cs[n - 1 & h]) == null) {
// 没有在执行扩容
if (cellsBusy == 0) { // Try to attach new Cell
// 创建新的 Cell 并写入值
final Cell r = new Cell(x); // Optimistically create
// 原子更新 cellsBusy
if (cellsBusy == 0 && casCellsBusy()) {
try { // Recheck under lock
Cell[] rs; int m, j;
// 再次确认目标 cell 是否为 null
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = m - 1 & h] == null) {
// 写入新创建的 cell,操作完成
rs[j] = r;
break done;
}
} finally {
// 重置 cellsBusy
cellsBusy = 0;
}
continue; // Slot is now non-empty
}
}
collide = false;
}
/**
* 1)基于线程探测值定位的 cell 不为 null
* 2)发生 cell 竞争,则重置
*/
else if (!wasUncontended) {
wasUncontended = true; // Continue after rehash
// 尝试原子更新目标 cell 中的值,更新成功则退出循环
} else if (c.cas(v = c.value,
fn == null ? v + x : fn.applyAsLong(v, x))) {
break;
// cells 数组长度超出系统的 CPU 总数或发生 cells 扩容
} else if (n >= Striped64.NCPU || cells != cs) {
collide = false; // At max size or stale
} else if (!collide) {
collide = true;
// 尝试进行扩容
} else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs) {
// 容量扩大为原来的两倍
cells = Arrays.copyOf(cs, n << 1);
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
// 基于伪随机数重新计算探测值
h = Striped64.advanceProbe(h);
}
// 2)未发生 cell 竞争 && cells 未扩容 && 原子更新 cellsBUsy 成功【表示当前 cell 正在写入值】
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try { // Initialize table
if (cells == cs) {
// 创建长度为 2 的 Cell 数组
final Cell[] rs = new Cell[2];
// 将目标值写入指定的 cell
rs[h & 1] = new Cell(x);
// 更新 cells table
cells = rs;
break done;
}
} finally {
// cells 创建完成,则重置标识
cellsBusy = 0;
}
}
// 3)尝试原子更新 base 值
else if (casBase(v = base,
fn == null ? v + x : fn.applyAsLong(v, x))) {
// 更新成功则退出循环
break done;
}
}
}
其他操作
/**
* 原子累加 1
*/
public void increment() {
add(1L);
}
/**
* 原子递减 1
*/
public void decrement() {
add(-1L);
}
/**
* 读取 LongAdder 的总和,计算过程中未发生竞争则其值是精确的。
*/
public long sum() {
final Cell[] cs = cells;
long sum = base;
if (cs != null) {
for (final Cell c : cs) {
if (c != null) {
sum += c.value;
}
}
}
return sum;
}
/**
* 只有在确保当前没有多线程竞争时,才应该调用该方法进行重置 LongAdder。
*/
public void reset() {
final Cell[] cs = cells;
base = 0L;
if (cs != null) {
for (final Cell c : cs) {
if (c != null) {
c.reset();
}
}
}
}
LongAdder 源码分析的更多相关文章
- 死磕 java并发包之LongAdder源码分析
问题 (1)java8中为什么要新增LongAdder? (2)LongAdder的实现方式? (3)LongAdder与AtomicLong的对比? 简介 LongAdder是java8中新增的原子 ...
- LongAdder源码分析
AtomicLong是作用是对长整形进行原子操作,显而易见,在java1.8中新加入了一个新的原子类LongAdder,该类也可以保证Long类型操作的原子性,相对于AtomicLong,LongAd ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- JDK源码分析(12)之 ConcurrentHashMap 详解
本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...
- ConcurrentHashMap JDK 1.6 源码分析
前言 前段时间把 JDK 1.6 中的 HashMap 主要的一些操作源码分析了一次.既然把 HashMap 源码分析了, 就顺便把 JDK 1.6 中 ConcurrentHashMap 的主要一些 ...
- 并发-ConcurrentHashMap源码分析
ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...
- 2. Sentinel源码分析—Sentinel是如何进行流量统计的?
这一篇我还是继续上一篇没有讲完的内容,先上一个例子: private static final int threadCount = 100; public static void main(Strin ...
- 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)
要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...
- ABP源码分析一:整体项目结构及目录
ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...
随机推荐
- Codeforces Round #503 (by SIS, Div. 2) E. Sergey's problem
E. Sergey's problem [题目描述] 给出一个n个点m条边的有向图,需要找到一个集合使得1.集合中的各点之间无无边相连2.集合外的点到集合内的点的最小距离小于等于2. [算法] 官方题 ...
- js实现计算器效果
<!DOCTYPE html> <html> <!-- Created using jsbin.com Source can be edited via http://j ...
- CSS中:first-child伪类
使用 :first-child 伪类来选择作为某个元素的第一个子元素.这个特定伪类很容易遭到误解,所以有必要举例来说明.考虑以下标记: 如下: html: <div> <p>T ...
- webpack自定义loader和自定义插件
1.封装自定义的功能loader (格式很简单,重点在于loader-utils,loaderUitls.getOptions可获取webpack配置rules中的options以供使用 ) 这只是一 ...
- Python 通过wmi获取Window服务器硬件信息
通过pip install wmi安装wmi 查看cpu序列号: wmic cpu get processorid 查看主板序列号: wmic baseboard get serialnumber 查 ...
- python之 yield --- “协程”
在编程中我们经常会用到列表,以前使用列表时需要声明和初始化,在数据量比较大的时候也需要把列表完整生产出来,例如要存放1000给数据,需要准备长度1000的列表,这样计算机就需要准备内存放置这个列表,在 ...
- msdn帮助,离线下载
这是我在msdn下载,如果要看msdn帮助,不是在线看就是visual studio 帮助那下载. 在网速不好的时候msdn看,会让人不爽. 帮助那个下载速度很慢,于是我就去下载离线. 因为微软看不到 ...
- Ubuntu 16.04安装N卡驱动、cuda、cudnn和tensorflow GPU版
安装驱动 最开始在英伟达官网下载了官方驱动,安装之后无法登录系统,在登录界面反复循环,用cuda里的驱动也出现了同样的问题.最后解决办法是把驱动卸载之后,通过命令行在线安装驱动. 卸载驱动: sudo ...
- 计蒜客 蓝桥模拟 H. 封印之门
Floyd算法,最短路,判断a,b是否相等. 代码: #include <cstdio> #include <cstdlib> #include <cstring> ...
- 正确理解MySQL中的where和having的区别
原文:https://blog.csdn.net/yexudengzhidao/article/details/54924471 以前在学校里学习过SQLserver数据库,发现学习的都是皮毛,今天以 ...