更胜一筹的LongAdder

AtomicLong

在Java8之前,我们经常使用AtomicLong来表示Long类型的一个原子类。众所周知,这个类的核心原理就是CAS,也就是比较并交换。CAS利用了CPU指令,使比较和交换两个操作成为一个原子操作。那么AtomicLong存在哪些缺陷呢?通过其源码可知,主要的实现逻辑是,在一个循环中不停地利用CAS设置值,直到CAS执行成功。在较低并发的情况下是没有问题的。在高并发时,大部分CAS都会执行失败,这样就会导致大部分CAS尝试都是失败的,CPU升高。

更胜一筹的LongAdder

AtomicLong部分源码

LongAdder

为了解决上面的问题,在Java8中增加了LongAdder类。这个类的核心思想是将集中的冲突分散,减少并发访问的冲突。在这个类中,由两个域来共同表示所代表的数据。一个是long base,另一个是Cell[] cells。

在累加一个值的时候,会有如下的过程:

更胜一筹的LongAdder

LongAdder源码

1.当cells为空的时候尝试使用!caseBase(b = base, b + x)把加数加到base上面。在不发生冲突时,流程走到这里就结束了。

2.如果cells不为空或者caseBase失败,说明还是存在冲突的。此时获取数组对应位上的cell,判断是否为空。不为空,则利用cas尝试设置。在低并发时,走到这里就结束了。

3.如果对应位的cell为空或者cas失败,这个时候就要进入longAccumulate函数了。在这个函数中,全部的逻辑都在一个for循环中:

a.如果对应位没有cell对象,那么创建一个cell对象,加入数组。

b.如果尝试创建新对象失败,那么尝试扩容cells。

c.如果数组大小已经达到上线,设置标志位,不再扩展。

d.尝试设置base。

逻辑一直在for里面循环,直到设置成功为止。

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {

int h;

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; int n; long v;

if ((as = cells) != null && (n = as.length) > 0) {

if ((a = as[(n - 1) & h]) == null) {

if (cellsBusy == 0) { //尝试创建新的cell

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;

}

else if (!wasUncontended) // CAS already known to fail

wasUncontended = true; // Continue after rehash

else if (a.cas(v = a.value, ((fn == null) ? v + x :

fn.applyAsLong(v, x))))

break;

else if (n >= NCPU || cells != as) //cell的数量已经达到最大了,不再扩容

collide = false; // At max size or stale

else if (!collide)

collide = true;

else if (cellsBusy == 0 && casCellsBusy()) {

try {

if (cells == as) { //扩容cell数组

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);

}

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;

}

else if (casBase(v = base, ((fn == null) ? v + x :

fn.applyAsLong(v, x))))

break; // Fall back on using base

}

}

sum方法

在sum方法里面,需要叠加base以及cells中全部值。

感想

每个类都要基于怎样的时候解决怎样的问题来设计。这个类之所以能够这么设计,主要是因为它只提供了累加的功能。如果提供了set方法,那么就没有办法再用这种设计了。没有万能的解决方案,凡是要结合实际情况来考虑。我们常提到的性能优化,除了缓存、并行之类的,优化自己的业务逻辑也是很重要的。比如,我们一直吐槽的12306。在最开始,都是安排在统一的一个时间点进行抢票,一瞬间就导致了很高的并发访问。在把抢票时间分开之后,这种优化效果要比直接通过技术的优化要好很多。


分享到:


相關文章: