众所周知,jdk提供了AtomicLong通过cas进行非阻塞的操作,但是他在高并发下,会造成大量的线程竞争失败后进行不断自旋,从而导致资源的大量浪费,所以又推出了LongAddr来克服高并发下的性能瓶颈。AtomicLong的思路是多个线程去竞争一个变量,而LongAddr的思路稍微不一样,就是将单个变量改为多个变量,从而提高并发下的性能
查看LongAddr构造函数,发现他只有一个默认的构造函数且没有定义任何全局变量,但是发现他继承了Striped64类,所以变量应该就在父类上了
java
public class LongAdder extends Striped64 implements Serializable {
private static final long serialVersionUID = 7249069246863182397L;
/**
* Creates a new adder with initial sum of zero.
*/
public LongAdder() {
}
查看Striped64类
java
abstract class Striped64 extends Number {
//避免伪共享
@sun.misc.Contended static final class Cell {
// 保持内存可见
volatile long value;
Cell(long x) { value = x; }
//cas操作,保证原子性
final boolean cas(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
/** Number of CPUS, to place bound on table size */
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Table of cells. When non-null, size is a power of 2.
* 用来进行cas竞争
*/
transient volatile Cell[] cells;
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* 基准变量,如果并发量小的情况,避免初始化Cell数组,而直接base进行cas操作
*/
transient volatile long base;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
*/
transient volatile int cellsBusy;
回到LongAddr,查看最常用的increment方法和decrement方法也是同理,发现调用的是add方法
java
/**
* Equivalent to {@code add(1)}.
*/
public void increment() {
add(1L);
}
java
/**
* Base value, used mainly when there is no contention, but also as
* a fallback during table initialization races. Updated via CAS.
* 基准变量,如果并发量小的情况,避免初始化Cell数组,而直接base进行cas操作
*/
transient volatile long base;
/**
* Adds the given value.
*
* @param x the value to add
*/
public void add(long x) {
Cell[] as;
long b, v;
//当前cell数组长度
int m;
Cell a;
//cell为空,则进行cas操作,类似AtomicLong的自增操作
if ((as = cells) != null || !casBase(b = base, b + x)) {
//cell不为空,或者cell为空但自旋增加也失败
boolean uncontended = true;
if (//cell等于null或者为空数组
as == null || (m = as.length - 1) < 0 ||
//获取当前线程对应的Cell对象,getProbe()是当前线程中的threadLocalRandomProbe & m
//计算出当前线程对应的cell数组下标
(a = as[getProbe() & m]) == null ||
//有Cell冲突,则执行cas操作,失败则执行longAccumulate,成功则Cell执行自增操作
!(uncontended = a.cas(v = a.value, v + x)))
//如果cas操作失败,则说明当前线程对应的cell对象被加锁,则调用longAccumulate寻找新的cell对象
longAccumulate(x, null, uncontended);
}
}
因为LongAddr的Cell数组默认是为空的,首先会判断LongAddr的Cell数组是否为空,如果为空则直接base进行cas操作,主要是为了在线程较少的情况下避免创建Cell数组,减少空间的浪费。之后就是判断Cell是否有冲突,有冲突则对当前线程对应的Cell进行Cas操作,没有冲突则执行Striped64的longAccumulate方法
Striped64.longAccumulate方法逻辑还是比较复杂的
ini
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
// cell数组的索引
int h;
// 获取新的索引值,相当于重新rehash寻找新的cell变量
if ((h = getProbe()) == 0) {
//初始化
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
//是否有冲突
boolean collide = false; // True if last slot nonempty
for (;;) {
Cell[] as;
Cell a;
//当前线程的cell数组的长度
int n;
long v;
//cell数组不为null,且cell不是一个空数组
if ((as = cells) != null && (n = as.length) > 0) {
//当前线程的cell索引的值为空
if ((a = as[(n - 1) & h]) == null) {
//cellBusy表示当前是否有线程处于初始化的过程,0表示没有线程进行初始化,1表示有线程进行初始化
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
//自旋,竞争cellBusy锁
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
//无论如何都解锁
cellsBusy = 0;
}
//成功则结束创建,失败则继续自旋
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
//如果有Cell值已经存在,则对当前的Cell对象进行Cas操作
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
//限制cell数组大小小于等于当前cpu数量
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
else if (!collide)
collide = true;
//当前元素个数没有超过cpu总数,且没有进行初始化或者扩容操作,进行扩容
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
//rehash
h = advanceProbe(h);
}
//cell数组为空,则进行初始化
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
//初始化cell数组
Cell[] rs = new Cell[2];
//hash
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // Fall back on using base
}
}
看着很长,其实逻辑还是比较清晰的,这个方法主要的功能就是Cell的初始化和Cell数组的创建和扩容,而他的几个if分支也就对应了这几个功能,其余的分支就是对当前线程或者线程对应的cell进行cas操作
Cell初始化分支
java
/**
* Table of cells. When non-null, size is a power of 2.
* 用来进行cas竞争
*/
transient volatile Cell[] cells;
/**
* Spinlock (locked via CAS) used when resizing and/or creating Cells.
* cellBusy表示当前是否有线程处于初始化的过程,0表示没有线程进行初始化,1表示有线程进行初始化
*/
transient volatile int cellsBusy;
//Cell数组不为空,则进行Cell初始化
if ((as = cells) != null && (n = as.length) > 0) {
//当前线程的cell索引的值为空
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
Cell r = new Cell(x); // Optimistically create
//加锁,自旋竞争cellBusy锁,等于0表示当前没有线程进行Cell初始化操作
// casCellsBusy,cas修改为1
if (cellsBusy == 0 && casCellsBusy()) {
boolean created = false;
//Cell初始化
try { // Recheck under lock
Cell[] rs; int m, j;
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
//无论如何都解锁
cellsBusy = 0;
}
//成功则结束,失败则继续自旋,重新竞争
if (created)
break;
continue; // Slot is now non-empty
}
}
//当前Cell不为空,则表示有冲突
collide = false;
}
Cell数组扩容分支
ini
//限制cell数组大小小于等于当前cpu数量
else if (n >= NCPU || cells != as)
collide = false; // At max size or stale
//如果Cell数组有冲突,且Cell数组大学小于当前CPU的数量,则修改为未冲突,进行扩容
else if (!collide)
collide = true;
//如没有进行Cell初始化和数组扩容,则加锁,进行数组扩容
else if (cellsBusy == 0 && casCellsBusy()) {
try {
if (cells == as) { // Expand table unless stale
//每次都按2的倍数扩容
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
//解锁
cellsBusy = 0;
}
//重置collide
collide = false;
continue; // Retry with expanded table
}
Cell数组初始化
ini
//cell数组为空,则进行初始化
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
//初始化cell数组
Cell[] rs = new Cell[2];
//hash
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
//解锁
cellsBusy = 0;
}
if (init)
break;
}
总的来说,LongAddr的Cell数组最大跟当前的CPU数相同,多线程并发会在Cell数组找寻对应的Cell变量进行cas操作,如果Cas操作失败,则会重新hash,找寻新的未加锁的Cell来进行cas操作,这样就能很大程度的减少线程自旋的时间而LongAddr最后输出的值也就是cell数组的总和