AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用自循(CLH)方式不断地cas到特定的值,从而达到更新数据的目的。然而在线程竞争激烈的情况下,自循往往浪费很多计算资源才能达成预期效果。
面对自循的缺点,jdk1.8推出了LongAdder类,他的实现方式有点像concurrentHashMap一样,采用空间换时间的方式,提高在线程竞争激烈的情况下,提供计数的效率,接下来我们看看源码是怎么实现的。
看源码前,我们先明白源码做了些什么,这样有目的的看,往往事半功倍。
LongAdder内里面存在一个Cell数组,计数是将线程id进行hash算法后,得到一个小于等于当前计算机核数的值,根据该值定位到一个cell数组,然后操作该cell进行单独计数。最终求总计数值时候,把所有的cell数组值相加即可。这样将多个线程操作一个AtomicLong转变为多个线程操作多个cell的情况,自然竞争小了,效率高了。可是存储空间大了。
注意:cell类前的注解@sun.misc.Contended,该注解涉及到伪共享(false share)问题,不了解的可以百度一下,在此不做累述。
- static final int NCPU = Runtime.getRuntime().availableProcessors();
- final void longAccumulate(long x, LongBinaryOperator fn,
- boolean wasUncontended) {
- int h;
- //获取当前线程的probe值作为hash值。
- if ((h = getProbe()) == 0) {
- //如果probe值为0,强制初始化当前线程的probe值,这次初始化的probe值不会为0。
- ThreadLocalRandom.current();
- //再次获取probe值作为hash值。
- h = getProbe();
- //这次相当于再次计算了hash,所以设置未竞争标记为true。
- wasUncontended = true;
- }
- boolean collide = false;
- for (;;) {
- Cell[] as; Cell a; int n; long v;
- if ((as = cells) != null && (n = as.length) > 0) {
- //通过h从cell表中选定一个cell位置。
- if ((a = as[(n - 1) & h]) == null) {
- //如果当前位置没有cell,尝试新建一个。
- if (cellsBusy == 0) {
- //创建一个Cell。
- Cell r = new Cell(x);
- //尝试或者cellsBusy锁。
- if (cellsBusy == 0 && casCellsBusy()) {
- boolean created = false;
- try {
- Cell[] rs; int m, j;
- //在获取锁的情况下再次检测一下。
- if ((rs = cells) != null &&
- (m = rs.length) > 0 &&
- rs[j = (m - 1) & h] == null) {
- //设置新建的cell到指定位置。
- rs[j] = r;
- //创建标记设置为true。
- created = true;
- }
- } finally {
- //释放cellsBusy锁。
- cellsBusy = 0;
- }
- if (created)
- //如果创建成功,直接跳出循环,退出方法。
- break;
- //说明上面指定的cell的位置上有cell了,继续尝试。
- continue;
- }
- }
- //走到这里说明获取cellsBusy锁失败。
- collide = false;
- }
- //以下条件说明上面通过h选定的cell表的位置上有Cell,就是a。
- else if (!wasUncontended) // CAS already known to fail
- //如果之前的CAS失败,说明已经发生竞争,
- //这里会设置未竞争标志位true,然后再次算一个probe值,然后重试。
- wasUncontended = true; // Continue after rehash
- //这里尝试将x值加到a的value上。
- else if (a.cas(v = a.value, ((fn == null) ? v + x :
- fn.applyAsLong(v, x))))
- //如果尝试成功,跳出循环,方法退出。
- break;
- else if (n >= NCPU || cells != as)
- //如果cell表的size已经最大,或者cell表已经发生变化(as是一个过时的)。
- collide = false;
- else if (!collide)
- //设置冲突标志,表示发生了冲突,重试。
- collide = true;
- //尝试获取cellsBusy锁。
- else if (cellsBusy == 0 && casCellsBusy()) {
- try {
- //检测as是否过时。
- 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锁。
- cellsBusy = 0;
- }
- collide = false;
- //扩容cell表后,再次重试。
- continue;
- }
- //算出下一个hash值。
- h = advanceProbe(h);
- }
- //如果cell表还未创建,先尝试获取cellsBusy锁。
- else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
- boolean init = false;
- try {
- if (cells == as) {
- //初始化cell表,初始容量为2。
- Cell[] rs = new Cell[2];
- rs[h & 1] = new Cell(x);
- cells = rs;
- init = true;
- }
- } finally {
- //释放cellsBusy锁。
- cellsBusy = 0;
- }
- if (init)
- //初始化cell表成功后,退出方法。
- break;
- }
- //如果创建cell表由于竞争导致失败,尝试将x累加到base上。
- else if (casBase(v = base, ((fn == null) ? v + x :
- fn.applyAsLong(v, x))))
- break;
- }
- }
最后求cell值时候,把各个cell的value值相加即可