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 ...
随机推荐
- RabbitMq学习4-发布/订阅(Publish/Subscribe)
一.发布/订阅 分发一个消息给多个消费者(consumers).这种模式被称为“发布/订阅”. 为了描述这种模式,我们将会构建一个简单的日志系统.它包括两个程序——第一个程序负责发送日志消息,第二个程 ...
- js日期格式验证
js日期格式验证 <input type="text" maxLength='10' onkeyup='checkDate(this.value,jQuery(this)); ...
- django模板传入参数的处理方式与反向生成url
前端模板传入参数的处理方式 1.传入单个参数: 前端使用href="/sel-{{ row.0 }}.html, url使用url(r'sel-(.+).html',home.index), ...
- Xcode 运行时异常
一:unable to boot the ios simulator:模拟器异常 1.在添加了新的xcode版本调试包时,出现旧版模拟器不支持的情况,关闭旧版模拟器,重新运行 二:Could not ...
- Nginx 服务器配置
include:实现对配置文件所包含的文件设定 default_type:默认类型二进制流,当文件类型未定义使用这种方式,用浏览器访问 PHP 文件会出现 下载窗口 log_format:指定日志输出 ...
- python2和3的一些区别,编码方式
python2与python3的区别: #python2 print() print'abc' #range() xrange()生成器 #raw_input()#python3 #print('ab ...
- java web请求过程
小技巧: 1.浏览器缓存 Ctrl+F5组合键刷新页面,浏览器会直接向目标URL发送请求,而不会使用浏览器缓存,并会在HTTP请求header中增加下面的请求头来告诉服务器不使用服务器缓存 发现在re ...
- FAT12
FAT12 is one of FAT file system families,mostly used on 1.44MB floppy disk. FAT 's full name is File ...
- 前端框架之BootStrap的简单介绍
Bootstrap补充 一.一个小知识点 1.截取长屏的操作 2.设置默认格式 3.md,sm, xs 4.空格和没有空格的选择器 二.响应式介绍 - 响应式布局是什么? 同一个网页在不同的终端上呈现 ...
- java面向对象复习之一
目的: 复习如何实现代码的逻辑思路: 复习类的封装: 复习类和对象的创建使用和封装: 练习: 实现功能:人到超市买东西 抽出三个类: 人 超市 东西: 功能点: 买: 它们之间的联系:东西包含于超市 ...