目录
5、LongAdder类中的longAccumulate()方法
LongAdder的原理
AtomicLong 使用内部变量value保存着实际的long值,所有的操作都是针对该value变量进行的。也就是说,在高并发环境下,value变量其实是一个热点,也就是N个线程竞争一个热点。重试线程越多,就意味着CAS的失败概率更高,从而进入恶性CAS空自旋状态。
LongAdder 的基本思路是分散热点,将value值分散到一个数组中,不同线程会命中到数组的不同槽(元素)中,各个线程只对自己槽中的那个值进行CAS操作。这样热点就被分散了,冲突的概率就小很多。
使用LongAdder,即使线程数再多也不必担心,各个线程会分配到多个元素上去更新,增加元素个数,就可以降低value的“热度”,AtomicLong中的恶性CAS空自旋就解决了。
如果要获得完整的LongAdder存储的值,只要将各个槽中的变量值累加,返回最终累加之后的值即可。
LongAdder的实现思路与ConcurrentHashMap中分段锁的基本原理非常相似,本质上都是不同的线程在不同的单元上进行操作,这样减少了线程竞争,提高了并发效率。LongAdder的设计体现了空间换时间的思想,不过在实际高并发场景下,数组元素所消耗的空间可以忽略不计。
LongAdder的设计体现了空间换时间的思想,不过在实际高并发场景下,数组元素所消耗的空间可以忽略不计。
1、LongAdder实例的内部结构
LongAdder的内部成员包含一个base值和一个cells数组。在最初无竞争时,只操作base的值;当线程执行CAS失败后,才初始化cells数组,并为线程分配所对应的元素。
LongAdder中没有类似于AtomicLong中的getAndIncrement()或者incrementAndGet()这样的原子操作,所以只能通过increment()方法和longValue()方法的组合来实现更新和获取的操作。
2、基类Striped64内部三个重要的成员
/**
* 成员一:存放Cell的哈希表,大小为2的幂
*/
transient volatile Cell[] cells;
/**
* 成员二:基础值
* 1. 在没有竞争时会更新这个值
* 2. 在cells初始化时,cells不可用,也会尝试通过CAS操作值累加到base
*/
transient volatile long base;
/**
* 自旋锁,通过CAS操作加锁,
* 为0表示cells数组没有处于创建、扩容阶段
* 为1表示正在创建或者扩展cells数组,不能进行新Cell元素的设置操作
*/
transient volatile int cellsBusy;
Striped64内部包含一个base和一个Cell[]类型的cells数组,cells数组又叫哈希表。在没有竞争的情况下,要累加的数通过CAS累加到base上;如果有竞争的话,会将要累加的数累加到cells数组中的某个Cell元素里面。所以Striped64的整体值value为base+∑[0~n]cells。
Striped64的设计核心思路是通过内部的分散计算来避免竞争,以空间换时间。LongAdder的base类似于AtomicInteger里面的value,在没有竞争的情况下,cells数组为null,这时只使用base进行累加;而一旦发生竞争,cells数组就上场了。
cells数组第一次初始化长度为2,以后每次扩容都变为原来的两倍,一直到cells数组的长度大于等于当前服务器CPU的核数。为什么呢?同一时刻能持有CPU时间片去并发操作同一个内存地址的最大线程数最多也就是CPU的核数。
在存在线程争用的时候,每个线程被映射到cells[threadLocalRandomProbe &cells.length]位置的Cell元素,该线程对value所做的累加操作就执行在对应的Cell元素的值上,最终相当于将线程绑定到cells中的某个Cell对象上。
3、LongAdder类的longValue()方法
public long longValue() {
// longValue()方法调用了sum(),累加所有Cell的值
return sum();
}
/**
* 将多个cells数组中的值加起来的和就类似于AtomicLong中的value
*/
public long sum() {
Cell[] as = cells;
Cell a;
long sum = base;
if (as != null) {
// 累加所有cell的值
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
4、LongAdder类的add()方法
/**
* 自增1
*/
public void increment() {
add(1L);
}
/**
* 自减1
*/
public void decrement() {
add(-1L);
}
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if (
// CASE 1: cells数组不为null,说明存在争用;在不存在争用的时候,cells数组一定为null,一旦对base的cas操作失败,才会初始化cells数组。
(as = cells) != null ||
// CASE 2: 如果cells数组为null,表示之前不存在争用,并且此次casBase执行成功,表示基于base成员累加成功,add方法直接返回;
// 如果casBase方法执行失败,说明产生了第一次争用冲突,需要对cells数组初始化,此时即将进入内层if块。
!casBase(b = base, b + x)) {
if (
// CASE 3: cells没有初始化
as == null || (m = as.length - 1) < 0 ||
// CASE 4: 指当前线程的hash值在cells数组映射位置的Cell对象为空,意思是还没有其他线程在同一个位置做过累加操作。
(a = as[getProbe() & m]) == null ||
// CASE 5:指当前线程的哈希值在cells数组映射位置的Cell对象不为空,然后在该Cell对象上进行CAS操作,
// 设置其值为v+x(x为该Cell需要累加的值),但是CAS操作失败,表示存在争用。
!(uncontended = a.cas(v = a.value, v + x)))
// 如果CASE 3、CASE 4、CASE 5有一个为真,就进入longAccumulate()方法
longAccumulate(x, null, uncontended);
}
}
casBase方法很简单,就是通过UNSAFE类的CAS设置成员变量base的值为base+x(要累加的值)。
/**
* 使用CAS来更新base值
*/
final boolean casBase(long cmp, long val) {
return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}
5、LongAdder类中的longAccumulate()方法
longAccumulate()是Striped64中重要的方法,实现不同的线程更新各自Cell中的值,其实现逻辑类似于分段锁,具体的代码如下:、
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
int h;
if ((h = getProbe()) == 0) {
ThreadLocalRandom.current(); // force initialization
h = getProbe();
wasUncontended = true;
}
// 扩容意向,collide=true可以扩容,collide=false不可扩容
boolean collide = false;
// 自旋,一直到操作成功
for (; ; ) {
// as 表示cells引用
// a 表示当前线程命中的Cell
// n 表示cells数组长度
// v 表示期望值
Cell[] as;
Cell a;
int n;
long v;
// CASE1: 表示cells已经初始化了,当前线程应该将数据写入对应的Cell中
// 这个大的if分支有 3 个小分支
if ((as = cells) != null && (n = as.length) > 0) {
// CASE1.1: true表示下标位置的Cell为null,需要创建new Cell
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // cells数组没有处于创建、扩容阶段
Cell r = new Cell(x); // Optimistically create
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;
}
// CASE1.2:当前线程竞争修改失败,wasUncontended为false
// wasUncontended是add(…)方法传递进来的参数如果为false,
// 就表示cells已经被初始化,并且线程对应位置的Cell元素也已经被初始化,但是当前线程对Cell元素的竞争修改失败。
// 如果add方法中的条件语句CASE 5通过CAS尝试把cells[m%cells.length]位置的Cell对象的value值设置为v+x而失败了,
// 就说明已经发生竞争,就将wasUncontended设置为false。
// 如果wasUncontended为false,就需要重新计算prob的值,那么自旋操作进入下一轮循环。
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
// CASE 1.3:当前线程rehash过哈希值,CAS更新Cell
// 无论执行CASE1分支的哪个子条件,都会在末尾执行h=advanceProb()语句
// rehash出一个新哈希值,然后命中新的Cell,
// 如果新命中的Cell不为空,在此分支进行CAS更新,将Cell的值更新为a.value+x,
// 如果更新成功,就跳出自旋操作;否则还得继续自旋。
else if (a.cas(v = a.value, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break;
// CASE 1.4:调整扩容意向,然后进入下一轮循环
// 条件1:如果n≥NCPU条件成立,就表示cells数组大小已经大于等于CPU核数,扩容意向改为false,表示不扩容了;
// 如果该条件不成立,就说明cells数组还可以扩容,
// 条件2:如果cells != as为true,就表示其他线程已经扩容过了,也会将扩容意向改为false,表示当前循环不扩容了。
// 当前线程调到CASE1分支的末尾执行rehash操作重新计算prob的值,然后进入下一轮循环。
else if (n >= NCPU || cells != as)
collide = false; // 达到最大值,或者as值过期
// CASE 1.5: 如果!collide=true满足,就表示扩容意向不满足,
// 设置扩容意向为true,但是不一定真的发生扩容
// 然后进入CASE1分支末尾重新计算prob的值,接着进入下一轮循环。
if (!collide)
collide = true;
// CASE 1.6:真正扩容的逻辑
// 条件1:cellsBusy==0为true表示当前cellsBusy的值为0(无锁状态),当前线程可以去竞争这把锁
// 条件2:casCellsBusy()表示当前线程获取锁成功,CAS操作cellsBusy改为0成功,可以执行扩容逻辑。
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
}
h = advanceProbe(h); //重置(rehash)当前线程Hash值
}
// CASE 2:cells还未初始化(as 为null),并且cellsBusy加锁成功,初始化cells
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
boolean init = false;
try { // Initialize table
if (cells == as) {
Cell[] rs = new Cell[2];
rs[h & 1] = new Cell(x);
cells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
// CASE 3:当前线程cellsBusy加锁失败,表示其他线程正在初始化cells
// 所以当前线程将值累加到base,注意add(…)方法调用此方法时fn为null
else if (casBase(v = base, ((fn == null) ? v + x :
fn.applyAsLong(v, x))))
break; // 在 base操作成功时跳出自旋
}
}
6、LongAdder类的casCellsBusy()方法
casCellsBusy()方法的代码,就是将cellsBusy成员的值改为1,表示目前的cells数组在初始化或扩容中
final boolean casCellsBusy() {
return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
}
casCellsBusy()相当于锁的功能: 当线程需要cells数组初始化或扩容时,需要调用casCellsBusy(),通过CAS方式将cellsBusy成员的值改为1,如果修改失败,就表示其他的线程正在进行数组初始化或扩容的操作。只有CAS操作成功,cellsBusy成员的值被改为1,当前线程才能执行cells数组初始化或扩容的操作。在cells数组初始化或扩容的操作执行完成之后,cellsBusy成员的值被改为0,这时不需要进行CAS修改,直接修改即可,因为不存在争用。
当cellsBusy成员值为1时,表示cells数组正在被某个线程执行初始化或扩容操作,其他线程不能进行以下操作:
(1)对cells数组执行初始化。
(2)对cells数组执行扩容。
(3)如果cells数组中某个元素为null,就为该元素创建新的Cell对象。因为数组的结构正在修改,所以其他线程不能创建新的Cell对象。