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. RabbitMq学习4-发布/订阅(Publish/Subscribe)

    一.发布/订阅 分发一个消息给多个消费者(consumers).这种模式被称为“发布/订阅”. 为了描述这种模式,我们将会构建一个简单的日志系统.它包括两个程序——第一个程序负责发送日志消息,第二个程 ...

  2. js日期格式验证

    js日期格式验证 <input type="text" maxLength='10' onkeyup='checkDate(this.value,jQuery(this)); ...

  3. django模板传入参数的处理方式与反向生成url

    前端模板传入参数的处理方式 1.传入单个参数: 前端使用href="/sel-{{ row.0 }}.html, url使用url(r'sel-(.+).html',home.index), ...

  4. Xcode 运行时异常

    一:unable to boot the ios simulator:模拟器异常 1.在添加了新的xcode版本调试包时,出现旧版模拟器不支持的情况,关闭旧版模拟器,重新运行 二:Could not ...

  5. Nginx 服务器配置

    include:实现对配置文件所包含的文件设定 default_type:默认类型二进制流,当文件类型未定义使用这种方式,用浏览器访问 PHP 文件会出现 下载窗口 log_format:指定日志输出 ...

  6. python2和3的一些区别,编码方式

    python2与python3的区别: #python2 print() print'abc' #range() xrange()生成器 #raw_input()#python3 #print('ab ...

  7. java web请求过程

    小技巧: 1.浏览器缓存 Ctrl+F5组合键刷新页面,浏览器会直接向目标URL发送请求,而不会使用浏览器缓存,并会在HTTP请求header中增加下面的请求头来告诉服务器不使用服务器缓存 发现在re ...

  8. FAT12

    FAT12 is one of FAT file system families,mostly used on 1.44MB floppy disk. FAT 's full name is File ...

  9. 前端框架之BootStrap的简单介绍

    Bootstrap补充 一.一个小知识点 1.截取长屏的操作 2.设置默认格式 3.md,sm, xs 4.空格和没有空格的选择器 二.响应式介绍 - 响应式布局是什么? 同一个网页在不同的终端上呈现 ...

  10. java面向对象复习之一

    目的: 复习如何实现代码的逻辑思路: 复习类的封装: 复习类和对象的创建使用和封装: 练习: 实现功能:人到超市买东西 抽出三个类: 人 超市 东西: 功能点: 买: 它们之间的联系:东西包含于超市 ...