美文网首页
多线程-LongAdder&LongAccumulator

多线程-LongAdder&LongAccumulator

作者: 麦大大吃不胖 | 来源:发表于2021-02-19 22:35 被阅读0次

by shihang.mai

1. LongAdder

出现的目的:AtomicLong其实性能已经很好,但是它有一个弊端,就是当高并发下,多个线程竞争一个变量,并且只有一个线程能够CAS成功,其他全部自旋。而LongAdder的思想,就是将这个竞争分散,用base值+cell数组中元素的值 = 最终值解决。

1.1 LongAdder数据结构

LongAdder数据结构.png

1.2 Cell元素定位

public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            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);
        }
    }
  1. 首先会去CAS设置base的值,成功并不会去更新Cell里面的值。

  2. 当CAS设置base值失败,就会通过getProbe()与数组长度-1取模,定位到某一个Cell,然后CAS设置其值。其中数组长度是2的N次方,这是为了和hashmap的作用一样。

1.3 初始化Cell数组

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;
            }
  1. 先CAS设置cellsBusy状态位=1
  2. 初始化长度为2的Cell数组,并且通过threadLocalRandomProbe&1,定位Cell位置,并设置值。
  3. 将cellsBusy重新变为0

1.4 Cell数组扩容

else if (n >= NCPU || cells != as)
                   collide = false;            // At max size or stale
else if (!collide)
                   collide = true; 
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
                }

扩容条件:当前cells数组长度小于当前机器CPU核数&&在Cell上CAS失败

  1. 首先通过cas设置cellsBusy状态位为1,然后将其原来数组长度扩容2倍
  2. 将原来的数据复制到新的cell数组

1.5 细节的巧妙

  • 线程访问分配的Cell元素有冲突后如何处理

重新计算当前线程的随机值threadLocalRandomProbe,减少下次访问cells元素时冲突的机会

  • 如何保证线程操作被分配的Cell元素的原子性
@sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

        // Unsafe mechanics
        private static final sun.misc.Unsafe UNSAFE;
        private static final long valueOffset;
        static {
            try {
                UNSAFE = sun.misc.Unsafe.getUnsafe();
                Class<?> ak = Cell.class;
                valueOffset = UNSAFE.objectFieldOffset
                    (ak.getDeclaredField("value"));
            } catch (Exception e) {
                throw new Error(e);
            }
        }
    }

Cell类有一个volatile修饰的value保证线程间的可见性,用CAS保证线程更新时的线程安全。还有@Contended防止缓存伪共享

2. LongAccumulator

LongAdder只是它的一个特例,LongAccumulator是个累加器

public LongAccumulator(LongBinaryOperator accumulatorFunction,
                      long identity) {
   this.function = accumulatorFunction;
   base = this.identity = identity;
}

@FunctionalInterface
public interface LongBinaryOperator {

/**
    * Applies this operator to the given operands.
    *
    * @param left the first operand
    * @param right the second operand
    * @return the operator result
    */
   long applyAsLong(long left, long right);
}

下面使用LongAccumulator来模拟LongAdder

public class TestLongAccumulator {

   public static void main(String[] args) {
       LongAccumulator longAccumulator = new LongAccumulator((a, b) -> {
           return a + b;
       }, 0);
       for (int i = 0; i < 1000; i++) {
           longAccumulator.accumulate(1);
           System.out.println(longAccumulator);
       }
   }
}

就是一个可以自定义行为的"累加器",其实可以做加减乘除的

相关文章

网友评论

      本文标题:多线程-LongAdder&LongAccumulator

      本文链接:https://www.haomeiwen.com/subject/bgyzxltx.html