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 ...
随机推荐
- 小白如何入门 Python 爬虫?
本文针对初学者,我会用最简单的案例告诉你如何入门python爬虫! 想要入门Python 爬虫首先需要解决四个问题 熟悉python编程 了解HTML 了解网络爬虫的基本原理 学习使用python爬虫 ...
- jquery做一个小的轮播插件---有BUG,后续修改
//首页无缝轮播 ; (function($, window, document, undefined) { $.fn.slider = function(options) { var default ...
- 计算机体系结构总结_Pipeline
Textbook:<计算机组成与设计——硬件/软件接口> HI<计算机体系结构——量化研究方法> QR 在前面一节里我们有了一块简单的RISC CPU,包括 ...
- __bridge
Core Foundation 框架Core Foundation框架 (CoreFoundation.framework) 是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能.下面列举 ...
- Docker下载镜像并创建容器运行
在linux系统中安装完成docker后,我们开始进行docker的镜像.容器的使用. 在使用docker时,首先要明确的两个概念:image(镜像) 与 container (容器) image: ...
- Verilog-2001标准在2001年就发布了
,不过翻了一些Verilog书籍,对Verilog-2001的新增特性很少有提及,即使提到了,也只是寥寥数语带过,其实在Verilog-2001中做了很多有用的改进,给编程带来很大的帮助,有必要详 ...
- Error response from daemon: Container ************** is not running
$ sudo docker run -d centos:last 8022826ebd567e2b1818e90ce33c3b68ea9aeac0286001154eb05fc2283e0238$ s ...
- STM32F407 跑马灯实验
1.库函数版本调用的函数有哪些?对应的源文件/头文件是哪个? 库函数 源文件 头文件 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE) stm3 ...
- 基于linux(CentOS7)数据库性能优化(Postgresql)
基于CentOS7数据库性能优化(Postgresql) 1. 磁盘 a) Barriers IO i. 通过查看linux是否加载libata,确定是否开 ...
- 计蒜客 蓝桥模拟 F. 结果填空:数独
代码: #include <cstdio> #include <cstdlib> #include <cstring> #include <iostream& ...