java版本11.0.1,感觉写得太水了,等心情好的时候再重新编辑一下。

LongAdder中的核心逻辑主要由java.util.concurrent.atomic.Striped64维护,作为Striped64的继承类LongAdder定义了(LongAccumulator、DoubleAdder、DoubleAccumulator...)一些外围逻辑

    /**
* Cell(单元)表,不为null时大小为2的幂
*/
transient volatile Cell[] cells; /**
* 基值,主要用在没有竞争访问时使用, 也会用在竞争创建cells失败时的备选方案。CAS更新
*/
transient volatile long base; /**
* cells的自旋锁,在创建/扩容cells时会用到
*/
transient volatile int cellsBusy;

sum()遍历cells累加和base,reset()遍历cells和base赋值0,sumThenReset()遍历cells和base用CAS操作累加并赋值0。比较简单就这么概括一下。

主要方法LongAdder#add

  public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
/**
* 当一开始没有竞争调用时,CAS操作base值累加.当开始出现竞争时开始走下面的逻辑,不再累加base
*/
if ((cs = cells) != null || !casBase(b = base, b + x)) {
/**
* 设置“无竞争”标识
*/
boolean uncontended = true;
/**
* 当cells未初始化时继续以下判断逻辑,否则调用longAccumulate()
*/
if (cs == null || (m = cs.length - 1) < 0 ||
/**
* 根据访问线程的特征值(probeValue)获取cells中访问线程对应的cell. cell未初始化时调用longAccumulate()
* 线程特征值和cells.length的&操作确保cs[getProbe()&m]不会越界
*/
(c = cs[getProbe() & m]) == null ||
/**
* 对访问线程对应的cell的值CAS操作,并把执行结果赋值uncontended.若CAS操作失败则调用longAccumulate()
*/
!(uncontended = c.cas(v = c.value, v + x))) { longAccumulate(x, null, uncontended);
}
}
}

核心逻辑藏在了Striped64#longAccumulate,稍微花时间瞄了两眼。

  /**
* 处理涉及到初始化、扩容、创建及碰撞更新cell的情况.
*
* @param x 运算值
* @param fn 运算操作,null时为加法
* @param wasUncontended 调用该方法前CAS操作的结果,CAS操作失败则为false
*/
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
/**
* 线程特征值
*/
int h;
/**
* 当调用线程是第一次调用longAccumulate()时,赋值线程的特征值
*/
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current();
h = getProbe();
wasUncontended = true;
}
/**
* 表示对cell的获取是否与其他线程碰撞, 用来判断cells是否需要扩容
*/
boolean collide = false;
/**
* 未获取到cellsBusy时则自旋
*/
done: for (;;) {
Striped64.Cell[] cs; Striped64.Cell c; int n; long v;
/**
* 当cells已存在并且不为空时
*/
if ((cs = cells) != null && (n = cs.length) > 0) {
/**
* 当访问线程对应的cell尚未存在时,新增Cell(x)
*/
if ((c = cs[(n - 1) & h]) == null) {
/**
* 尝试获取自旋锁
*/
if (cellsBusy == 0) {
/**
* (乐观)创建Cell
*/
Striped64.Cell r = new Striped64.Cell(x);
/**
* 尝试获取自旋锁
*/
if (cellsBusy == 0 && casCellsBusy()) {
try {
Striped64.Cell[] rs; int m, j;
/**
* 在持有cellsBusy锁的情况下再次检查访问线程对应的cell是否已存在
*/
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
/**
* 新增Cell并且跳出自旋
*/
rs[j] = r;
break done;
}
} finally {
cellsBusy = 0;
}
/**
* 新增Cell失败,自旋
*/
continue;
}
}
/**
* 如果尝试获取自旋锁失败,说明已有其他线程占用了该Cell,
* 之后为减少碰撞会调用advanceProbe()
*/
collide = false;
}
/**
* 如果先前的cs[getProbe()&m]的CAS累加操作失败, 则wasUncontended赋值true
*/
else if (!wasUncontended)
wasUncontended = true;
/**
* 对cell执行CAS操作,成功则方法结束,失败则自旋
*/
else if (c.cas(v = c.value,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break;
/**
* 当cells大小大于逻辑cpu数,不扩容
*/
else if (n >= NCPU || cells != cs)
collide = false;
/**
* 通过collide是否碰撞判断是否执行下面的扩容逻辑
* collide==false时则自旋
*/
else if (!collide)
collide = true;
/**
* 执行到这里说明需要扩容
* 尝试获取cellsBusy锁扩容
*/
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == cs) // Expand table unless stale
cells = Arrays.copyOf(cs, n << 1);
} finally {
cellsBusy = 0;
}
collide = false;
continue;
}
/**
* 这是为了重新计算访问线程的特征值(advanceProbe(h))后自旋,减少碰撞
*/
h = advanceProbe(h);
}
/**
* 当cells没有正在初始化/扩容(cellsBusy == 0)并且cells未被创建(cells == cs)时,则设置cells的自旋锁cellBusy,开始创建cells对象
*/
else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
try {
/**
* 初始化cells和访问线程对应的Cell对象
*/
if (cells == cs) {
Striped64.Cell[] rs = new Striped64.Cell[2];
rs[h & 1] = new Striped64.Cell(x);
cells = rs;
break done;
}
} finally {
/**
* cells创建完成后释放cellBusy锁
*/
cellsBusy = 0;
}
}
/**
* 以上判断条件失败,则走备选逻辑:CAS操作运算base值(计算sum时会加上base值)
*/
else if (casBase(v = base,
(fn == null) ? v + x : fn.applyAsLong(v, x)))
break done;
}
}

懒得画流程图了~

瞄一眼LongAdder(jdk11)的更多相关文章

  1. 瞄一眼CopyOnWriteArrayList(jdk11)

    CopyOnWriteArrayList是ArrayList线程安全的变体.使用写时复制策略进行修改操作. 与之前版本较明显的区别是,jdk11中用来保护所有设值方法(mutator)的Reentra ...

  2. 瞄一眼,带你走进SparkSQL的世界

    本文由  网易云发布. 作者:范欣欣(本篇文章仅限知乎内部分享,如需转载,请取得作者同意授权.) 最近想来,大数据相关技术与传统型数据库技术很多都是相互融合.互相借鉴的.传统型数据库强势在于其久经考验 ...

  3. Java并发编程实战笔记

    如果当多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就会出现错误.有三种方式可以修复这个问题: i.不在线程之间共享该状态变量 ii.将状态变量修改为不可变的变量 iii.在访问状态变 ...

  4. 后HTML5时代

    十二年前,无论多么复杂的布局,在我们神奇的table面前,都不是问题:十年前,阿捷的一本<网站重构>,为我们开启了新的篇章:八年前,我们研究yahoo.com,惊叹它在IE5下都表现得如此 ...

  5. [深入JUnit] 测试运行的入口

    阅读前提 了解JUnit 对JUnit的内部实现有兴趣 不妨看看[深入JUnit] @Before, @After, @Test的秘密] 代码版本: junit 4.12代码搜索工具: http:// ...

  6. Intellij IDEA 一些不为人知的技巧

    Intellij IDEA 一些不为人知的技巧 2016/12/06 | 分类: 基础技术 | 0 条评论 | 标签: IntelliJ 分享到:38 原文出处: khotyn 今天又听了 Jetbr ...

  7. jq绑定事件的4种方式

    jQuery提供了多种绑定事件的方式,每种方式各有其特点,明白了它们之间的异同点,有助于我们在写代码的时候进行正确的选择,从而写出优雅而容易维护的代码.下面我们来看下jQuery中绑定事件的方式都有哪 ...

  8. [No00007D]2016-面经[上]

    面试常见问题: 题一:"请你自我介绍一下" 思路:1.这是面试的必考题目.2.介绍内容要与个人简历相一致.3.表述方式上尽量口语化.4.要切中要害,不谈无关.无用的内容.5.条理要 ...

  9. [No00006D]下载离线版的github for windows【以Github for Windows 3.0.110.为例】

    目录 先上地址后讲原理: 原理: 11个目录的文件怎么一口气下载呢? 最后,把下好的文件批量名,同时将GitHub.exe.manifest也放到软件根目录下(与GitHub.exe同级): 今后的猜 ...

随机推荐

  1. 2717: 递归函数求n的阶乘

    2717: 递归函数求n的阶乘 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 1329  Solved: 942[Submit][Status][Web ...

  2. 队列的add与offer的区别

    两个方法都表示往队列里添加元素 但是当出现异常时,add方法抛出异常 而offer则返回的是false,就是啥事也没有,也不抛异常,也没有添加成功!

  3. C#数组排序方法

    在C#中常用的数组排序的方法有:选择排序法.冒泡排序法.插入排序法和希尔排序法等. 一.选择排序法 using System;using System.Collections.Generic;usin ...

  4. Java中什么是匿名对象,空参构造方法输出创建了几个匿名对象,属性声明成static

    package com.swift; //使用无参构造方法自动生成对象,序号不断自增 public class Person { private static int count; //如果在定义类时 ...

  5. [BZOJ] 1520: [POI2006]Szk-Schools

    费用流解决. abs内传不了int..CE一次 #include<iostream> #include<cstring> #include<cstdio> #inc ...

  6. Unity基础-编辑器

    编辑器 Special Folders Hidden Folder(start with .) Standard Assets:第一批加载的文件 Editor:只在编辑下才能使用, Plugins R ...

  7. java中的final关键字(2013-10-11-163 写的日志迁移

     final关键字:修饰符,表示最后的.最终的    修饰类: 表示该类不能派生子类(不能被继承)   1.当不希望父类的的某个方法被子类覆盖(override)时,可以用final关键字来修饰.   ...

  8. thinkphp5开发restful-api接口学习 笔记二

    目录 第4节 为api项目搭建数据库 第5节 使用markdown书写接口文档 第6节(判断数据库中是否有此用户) 第7节 为项目配置URL 需求分析 配置主域名和二级域名 使用tp5路由进行URL解 ...

  9. Python学习笔记:py2exe打包Python程序

    使用py2exe将一个Python程序打包成一个exe程序,这样Python程序也可以在没有安装Python的环境中运行Python程序了.使用这个工具需要写一个用于打包的setup.py文件(名称可 ...

  10. list 方法总结整理

    #!/usr/bin/env python #Python 3.7.0 列表常用方法 __author__ = "lrtao2010" #创建列表 # a = [] # b = [ ...