LongAdder
1.介绍
LongAdder是JDK8新增加的一个类,主要功能与AtomicLong一样都是做整型自增的功能,但是AtomicLong在高并发情况下性能较差所以才会有LongAdder
2.阿里巴巴开发手册的说明
3.AtomicLong为什么在高并发情况下性能差
AtomicLong内部使用CAS+自旋的方式完成工作,在高并发环境下,N个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong的过多的自旋失败会导致性能成为瓶颈
4.LongAdder与AtomicLong的性能测试
代码
public class TestLongAdder {
public static void main(String[] args) throws Exception {
testAtomicLongAdder(1, 10000000);
testAtomicLongAdder(10, 10000000);
testAtomicLongAdder(100, 10000000);
}
static void testAtomicLongAdder(int threadCount, int times) throws Exception {
System.out.println("threadCount: " + threadCount + ", times: " + times);
long start = System.currentTimeMillis();
testLongAdder(threadCount, times);
System.out.println("LongAdder 耗时:" + (System.currentTimeMillis() - start) + "ms");
System.out.println("threadCount: " + threadCount + ", times: " + times);
long atomicStart = System.currentTimeMillis();
testAtomicLong(threadCount, times);
System.out.println("AtomicLong 耗时:" + (System.currentTimeMillis() - atomicStart) + "ms");
System.out.println("----------------------------------------");
}
static void testAtomicLong(int threadCount, int times) throws Exception {
AtomicLong atomicLong = new AtomicLong();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
list.add(new Thread(() -> {
for (int j = 0; j < times; j++) {
atomicLong.incrementAndGet();
}
}));
}
for (Thread thread : list) {
thread.start();
}
for (Thread thread : list) {
thread.join();
}
System.out.println("AtomicLong value is : " + atomicLong.get());
}
static void testLongAdder(int threadCount, int times) throws Exception {
LongAdder longAdder = new LongAdder();
List<Thread> list = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
list.add(new Thread(() -> {
for (int j = 0; j < times; j++) {
longAdder.increment();
}
}));
}
for (Thread thread : list) {
thread.start();
}
for (Thread thread : list) {
thread.join();
}
System.out.println("LongAdder value is : " + longAdder.longValue());
}
}
结果
随着并发的增加,AtomicLong的性能急剧下降,耗时是LongAdder的数倍
5.为什么这么快
分段累加
在AtomicLong中所有的线程都是对AtomicLong中的value进行自增的这样会导致CAS失败次数较多,LongAdder的核心思想就是减少CAS失败次数,通过分段加锁的方式实现了减少CAS失败次数
消除伪共享
在LongAdder的父类Striped64中存在一个Cell数组,其长度是2的幂次,每个Cell都使用@Contended注解进行修饰,而 @Contended注解可以进行缓存行填充,从而解决缓存行伪共享的问题。伪共享会导致缓存行失效,缓存一致性开销变大
@sun.misc.Contended static final class Cell {
}
6.继承结构
7.LongAdder源码解析
介绍
LongAdder里面方法不少,但是咱们只看一个核心入口方法add和求和方法sum即可,其他核心逻辑都在父类里面
sum方法
public long sum() {
// 获得Cell数组
Cell[] as = cells; Cell a;
// 获得base
long sum = base;
// 如果as不是NULL说明发生了竞争需要对Cell数组求和
if (as != null) {
// 遍历Cell数组
for (int i = 0; i < as.length; ++i) {
// 获得当前Cell并不是NULL
if ((a = as[i]) != null)
// 累加sum
sum += a.value;
}
}
// 如果as是NULL则直接返回base,如果不是NULL则返回base+Cell数组的和
return sum;
}
add方法
public void add(long x) {
/*
* as:Cell数组
* b:表示获取的base值
* v:表示期望值
* m:表示celss数组的长度
* a:表示当前线程命中的cell单元格
*/
Cell[] as; long b, v; int m; Cell a;
/*
* (as = cells) != null:Cell不是NULL说明已经有竞争了,Cell数组已经初始化
* !casBase(b = base, b + x):多线程修改base变量了,发生了竞争导致失败
*
*/
if ((as = cells) != null || !casBase(b = base, b + x)) {
//
boolean uncontended = true;
/*
* as == null:说明是因为多线程修改base失败进来的
* (m = as.length - 1) < 0:Cell数组有并且长度不是0
* (a = as[getProbe() & m]) == null:对Cell数组取余找到当前线程的Cell下标,而且已经初始化对应Cell
* !(uncontended = a.cas(v = a.value, v + x)):修改当前线程对应的Cell的value如果失败则进入longAccumulate
*/
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null ||
!(uncontended = a.cas(v = a.value, v + x)))
longAccumulate(x, null, uncontended);
}
}
总结add方法的核心逻辑如下:
- 判断cells数组是否初始化,如果没有初始化则累加到base上
- 如果celss数组没有初始化,并且竞争base失败,则准备进入longAccumulate方法
- 如果cells数组已经初始化,并且当前线程对应的cell对象没有初始化,则进入longAccumulate方法
- 如果cells数组已经初始化,并且当前线程对应的cell对象已存在,那么尝试使用CAS修改对应cell对象的value,如果存在竞争修改失败,则进入longAccumulate方法
8.Striped64源码解析
静态内部类
// 静态内部类分段锁的操作对象,使用Contended注解解决缓存行伪共享的问题
@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实例
private static final sun.misc.Unsafe UNSAFE;
// value属性的内存偏移地址
private static final long valueOffset;
// 初始化Unsafe类和重要属性的内存偏移
static {
try {
// 通过Unsafe提供的方法获得Unsafe实例
UNSAFE = sun.misc.Unsafe.getUnsafe();
// 获取value的内存偏移量
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
核心属性
/**
* 获取系统的逻辑核数量,这个值控制着Cell对象的数量,一旦Cell对象数量大于了逻辑核数量那么会造成上下文切换频繁
*/
static final int NCPU = Runtime.getRuntime().availableProcessors();
/**
* Cell数组,长度是2的N次幂
*/
transient volatile Cell[] cells;
/**
* 未发生竞争时的累加单元
*/
transient volatile long base;
/**
* 初始化cells或者扩容cells需要获取锁, 0:表示无锁状态 1:表示其他线程已经持有了锁,类似于AQS的state
*/
transient volatile int cellsBusy;
辅助方法
/**
* 封装了对于base的CAS操作
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
/**
* 封装了对于cellsBusy的CAS操作
*/
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
/**
* 封装了对于probe的CAS操作
*/
static final int getProbe() {
return UNSAFE.getInt(Thread.currentThread(), PROBE);
}
/**
* 重新设置线程的Prob
*/
static final int advanceProbe(int probe) {
probe ^= probe << 13;
probe ^= probe >>> 17;
probe ^= probe << 5;
UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
return probe;
}
初始化Unsafe和其他偏移量的代码
/**
* Unsafe实例
*/
private static final sun.misc.Unsafe UNSAFE;
/**
* 各个属性内存偏移量
*/
private static final long BASE;
private static final long CELLSBUSY;
private static final long PROBE;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> sk = Striped64.class;
BASE = UNSAFE.objectFieldOffset
(sk.getDeclaredField("base"));
CELLSBUSY = UNSAFE.objectFieldOffset
(sk.getDeclaredField("cellsBusy"));
Class<?> tk = Thread.class;
PROBE = UNSAFE.objectFieldOffset
(tk.getDeclaredField("threadLocalRandomProbe"));
} catch (Exception e) {
throw new Error(e);
}
}
longAccumulate方法
有哪些情况会进入这个方法
- cells数组没有初始化并且CAS累加base失败
- cells数组已经初始化,但是当前线程对应的cell对象为空
- cells数组已经初始化,当前线程对应cell对象也不为null,但是对对应的cell对象进行CAS累加失败。
方法入参
- long x:需要增加的值,一般默认都是1
- LongBinaryOperator fn:默认传递的是null
- wasUncontended:竞争标识,如果是false则代表有竞争。只有cells初始化之后,并且当前线程CAS竞争修改指定cell对象失败,才会是false
方法的作用
- 初始化Celss数组并找到当前线程对应的Cell对象并累加
- 初始化当前线程对应的Cell对象并累加
- 对指定Cell单元的累加操作进行重试
源码
final void longAccumulate(long x, LongBinaryOperator fn,
boolean wasUncontended) {
// 存储当前线程的probe
int h;
// 如果是0则表示没有初始化
if ((h = getProbe()) == 0) {
// 获取一个值
ThreadLocalRandom.current();
// 再次获取probe给h
h = getProbe();
wasUncontended = true;
}
// 用于标记是否发生冲突
boolean collide = false;
// 自旋
for (;;) {
Cell[] as; Cell a; int n; long v;
/*
* (as = cells) != null:Cell数组已经初始化
* (n = as.length) > 0:Cell数组长度大于0
* 总的来说就是判断Cell已经初始化并且有长度
*/
if ((as = cells) != null && (n = as.length) > 0) {
// 获取对应的Cell,如果为空,则说明没有初始化
if ((a = as[(n - 1) & h]) == null) {
// 表示当前没有其他线程创建或扩容Cell数组,也没有线程创建Cell
if (cellsBusy == 0) {
// 创建一个Cell
Cell r = new Cell(x);
// 如果还是0则尝试CAS修改cellsBusy从0->1
if (cellsBusy == 0 && casCellsBusy()) {
// 是否创建成功,默认为false不成功
boolean created = false;
try {
Cell[] rs; int m, j;
// 重新获取Cell数组然后检查然后把Cell初始化进去
if ((rs = cells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
// 表示当前没有其他线程创建或扩容Cell数组,也没有线程创建Cell
cellsBusy = 0;
}
// 创建成功就break跳出循环,因为Cell里面装的已经是要累加的值了直接结束逻辑即可
if (created)
break;
// 否则继续循环
continue;
}
}
// CAS修改cellsBusy为1失败就会来到这里
collide = false;
}
// 走到这个条件说明当前线程对应的cell对象不为空,如果这个条件成立说明是因为有竞争才进入longAccumulate方法的,然后会去重置竞争标记为true也就是没有竞争,因为自旋后面回rhash
else if (!wasUncontended)
wasUncontended = true;
// 再次尝试CAS更新当前线程所在Cell的值,如果成功了就返回
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
// 如果Cell数组的长度达到了CPU核心数,或者Cell数组扩容了
else if (n >= NCPU || cells != as)
// 设置false并通过下面的语句修改线程的probe再重新尝试
collide = false;
else if (!collide)
collide = true;
// 如果cellsBusy == 0并且CAS设置为1成功,则尝试扩容Cell数组
else if (cellsBusy == 0 && casCellsBusy()) {
try {
// 如果还是那个Cell
if (cells == as) {
// 扩容
Cell[] rs = new Cell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
cells = rs;
}
} finally {
// 重置标记为0
cellsBusy = 0;
}
// 已解决冲突
collide = false;
// 扩容完成后重新尝试
continue;
}
// 重新计算h值
h = advanceProbe(h);
}
// 如果Cell数组没有初始化则尝试初始化
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try {
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
// 如果有其他线程在初始化Cell数组中,就尝试更新base,如果成功就返回
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
}
}