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 源码分析的更多相关文章

  1. 死磕 java并发包之LongAdder源码分析

    问题 (1)java8中为什么要新增LongAdder? (2)LongAdder的实现方式? (3)LongAdder与AtomicLong的对比? 简介 LongAdder是java8中新增的原子 ...

  2. LongAdder源码分析

    AtomicLong是作用是对长整形进行原子操作,显而易见,在java1.8中新加入了一个新的原子类LongAdder,该类也可以保证Long类型操作的原子性,相对于AtomicLong,LongAd ...

  3. 死磕 java集合之ConcurrentHashMap源码分析(三)

    本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...

  4. JDK源码分析(12)之 ConcurrentHashMap 详解

    本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...

  5. ConcurrentHashMap JDK 1.6 源码分析

    前言 前段时间把 JDK 1.6 中的 HashMap 主要的一些操作源码分析了一次.既然把 HashMap 源码分析了, 就顺便把 JDK 1.6 中 ConcurrentHashMap 的主要一些 ...

  6. 并发-ConcurrentHashMap源码分析

    ConcurrentHashMap 参考: http://www.cnblogs.com/chengxiao/p/6842045.html https://my.oschina.net/hosee/b ...

  7. 2. Sentinel源码分析—Sentinel是如何进行流量统计的?

    这一篇我还是继续上一篇没有讲完的内容,先上一个例子: private static final int threadCount = 100; public static void main(Strin ...

  8. 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)

    要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...

  9. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

随机推荐

  1. P2220 [HAOI2012]容易题

    传送门 首先 $(\sum_{i=1}^{n}a_i)(\sum_{i=1}^{m}b_i)$ 展开以后包含了所有 $ab$ 两两相乘的情况并且每种组合只出现一次 发现展开后刚好和题目对序列价值的定义 ...

  2. MySQL索引优化与分析(重要)

    建表SQL CREATE TABLE staffs ( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR (24) NULL DEFAULT '' COM ...

  3. VS 2012 Unit Test

    1,Open Tool->Custmoize 2,Create Unit Tests Move Down Run Test 3,Restart run VS 4,Create UnitTest ...

  4. Beta冲刺-(2/3)

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/ 这个作业要求在哪里 https://edu.cnbl ...

  5. JS让函数只调用一次

    1 .  在第一次调用函数时,就将该函数内容腾空,以到达函数仅调用一次 ———————————————————————————————— 2 . 设置布尔值来控制后面的函数调用 window.onlo ...

  6. layui token 过期 重新登陆

    这个方法你要全局设置     //jquery全局配置 $.ajaxSetup({     cache: false,     crossDomain: true,       headers :{' ...

  7. bzoj4383 [POI2015]Pustynia 拓扑排序+差分约束+线段树优化建图

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=4383 题解 暴力的做法显然是把所有的条件拆分以后暴力建一条有向边表示小于关系. 因为不存在零环 ...

  8. 关于mysql(Navicat premium软件中) 外键设置中“删除”和“更新”选项详解

    ON DELETE restrict(约束):当在父表(即外键的来源表)中删除对应记录时,首先检查该记录是否有对应外键,如果有则不允许删除. no action:意思同restrict.即如果存在从数 ...

  9. css3 宽度百分比减去固定宽度 无效问题

    一定要注意中间横线的间距才有效果 正确 width: calc(50% - 10px); 错误 width:calc(50%-10px);

  10. scoket、TCP、UDP、WebService选型

    抱着去转型产品经理的方向去面试了一家公司,面试完很惭愧,不过见到了人事我也很意外,因为其实表现也没那么好,不过在此谈谈我的感受. 1.有3轮,前2轮都是先让我做自我介绍(我都说的很干脆,直接哪年毕业, ...