AtomicLong和LongAdder的区别

并发场景下的number操作,都会选用java.util.concurrent.atomic包下的数据结构,比如典型的计数场景,而AtomicLong是其中一个,但是jdk1.8后Doug Lea大神又添加LongAdder和DoubleAdder,在大并发场景下性能会远高于AtomicLong,看下源码了解下原理。
首先看下AtomicLong是怎么保证原子操作,以add操作,拿其中 addAndGet 方法:

public final long addAndGet(long delta) {
    return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}    

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        //取出旧的值用于cas操作
        var6 = this.getLongVolatile(var1, var2);
        //用CAS来保证原子性,如果操作失败就循环,var6+var4是新值
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}

可以看出是通过底层cas操作保证原子操作,这里看下这个valueOffSet

static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicLong.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}  

可以看出这个valueOffSet实际是当前AtomicLong实例的value属性在内存中的偏移量,看到这里就知道了通过CAS原子更新value的值。
AtomicLong实际是多个线程竞争修改这个value属性的机会。那优化锁竞争的一个思路就是将锁的粒度细化,比如之前concurrentHashMap的分段锁也是这个优化思路,那AtomicLong怎么将锁优化而又不改变最终的计算结果呢…
比如我需要将100 加个10 再加20,那么有下面这两种操作步骤:

  • 第一种:先将100+10 = 110,然后再110+20 得到最终结果 130。
  • 第二种:将100拆成2个50,在第一个50上面加10得到60,在最后一个50上加20得到70,最后将第一操作结果 60 和 第二次的操作结果70相加,得到最终的130。

LongAdder就是第二种操作思路,将原AtomicLong里的value拆成多个value,这样在并发情况下,就将多个线程竞争修改【一个】value属性的机会 --> 多个线程竞争修改【多个】value属性的机会,这样就相当于将性能翻倍。思路了解了,下面来看下LongAdder的代码:
以LongAdder的add方法为例:

public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    if ((as = cells) != null || !casBase(b = base, b + x)) {//先进行一次CAS操作,如果操作不成功说明有竞争,则进入分段操作的逻辑
        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);
    }
}  
    

这里的一个重要属性cells就是将value进行拆分的集合,比如将1000 拆成 200、400、400 那么这个cells就是一个长度为3的数组。 下面看下 longAccumulate方法逻辑:

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) {       // Try to attach new 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)
                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
            }
            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
    }
}    

主要看65-66行,这 个是cells初始化的地方,初始是2个长度,即将value默认是拆成2段,然后看下47-51这个是扩容的地方,扩容的时机是cellsBusy==0,从字面意思理解应该是整个cells的子元素都被线程占用了,那么就开始扩容到原来的两倍,看到这里LongAdder优化的思路及一些细节基本就清楚了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值