AtomicLong
在Java8之前,我们经常使用AtomicLong来表示Long类型的一个原子类。众所周知,这个类的核心原理就是CAS,也就是比较并交换。CAS利用了CPU指令,使比较和交换两个操作成为一个原子操作。那么AtomicLong存在哪些缺陷呢?通过其源码可知,主要的实现逻辑是,在一个循环中不停地利用CAS设置值,直到CAS执行成功。在较低并发的情况下是没有问题的。在高并发时,大部分CAS都会执行失败,这样就会导致大部分CAS尝试都是失败的,CPU升高。
LongAdder
为了解决上面的问题,在Java8中增加了LongAdder类。这个类的核心思想是将集中的冲突分散,减少并发访问的冲突。在这个类中,由两个域来共同表示所代表的数据。一个是long base,另一个是Cell[] cells。
在累加一个值的时候,会有如下的过程:
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。在最开始,都是安排在统一的一个时间点进行抢票,一瞬间就导致了很高的并发访问。在把抢票时间分开之后,这种优化效果要比直接通过技术的优化要好很多。
閱讀更多 java技術小碼農 的文章