资讯专栏INFORMATION COLUMN

【Java并发】浅析 AtomicLong & LongAdder

zhjx922 / 3024人阅读

摘要:最终依旧使用来更新值。此时使用能更好地提升性能。适用于高并发情况下的计数操作,利用与相似的原理,以空间换时间,提高了实际的计数效率。

AtomicLong
/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final long incrementAndGet() {
    return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
unsafe
public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    // 关注重点:if(var2 == var6)
    return var6;
}

var1 调用原方法 incrementAndGet 即自身的对象

var2 原对象当前(工作内存中的)值

var4 要加上去的值

var6 调用底层方法 getLongVolatile 获得当前(主内存中的)值,如果没其他线程修改即与 var2 相等

compareAndSwapLong

var2var6 为什么可能会不一样?

在并发环境下,工作内存中的值 var2 与主内存中的值 var6 之间的可能不一样(JMM)

// 获取主内存里的值
public native long getLongVolatile(Object var1, long var2);
// CAS 操作
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如 C 和 C++)实现的文件中。Java 语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。
LongAdder
public void increment() {
    add(1L);
}

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); // <- 重点
    }
}
Cell

Cell 类,是一个普通的二元算术累积单元,它在 Striped64 里面。Striped64 这个类使用分段的思想,来尽量平摊并发压力(类似1.7及以前版本的 ConcurrentHashMap.Segment)。

最终依旧使用 compareAndSwapLong 来更新值。

/**
 * Padded variant of AtomicLong supporting only raw accesses plus CAS.
 *
 * JVM intrinsics note: It would be possible to use a release-only
 * form of CAS here, if it were provided.
 */
@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);
        }
    }
}
longAccumulate

Long 映射到 Cell[] 数组里面,通过 Hash 等算法映射到其中一个数字进行计数,而最终的计数结果就是其求和累加。在低并发的时候,通过对 base 的直接更新,可以很好地保证和 Atomic 性能的基本一致;而在高并发的时候,则将单点的更新压力分散到各个节点上,提升了性能。

总结

AtomicLong 适用于序号生成,这种情况下需要准确的、全局唯一的数值;但在高并发情况下的计数操作,使用 AtomicLong 时会因线程竞争导致失败白白循环一次;失败次数越多,循环次数也越多。此时使用LongAdder 能更好地提升性能。

LongAdder 适用于高并发情况下的计数操作,利用与 JDK1.7 ConcurrentHashMap 相似的原理,以空间换时间,提高了实际的计数效率。当然,线程竞争很低的情况下进行计数,使用 AtomicLong 还是更简单更直接,并且效率稍微高一些。

注意:CAS 是 sun.misc.Unsafe 中提供的操作,只对 int、long、对象类型(引用或者指针)提供了这种操作,其他类型都需要转化为这三种类型才能进行 CAS 操作。(例如 DoubleAdder 就是 LongAdder 的简单改造,主要的变化就是用 Double.longBitsToDoubleDouble.doubleToRawLongBits 对底层的8字节数据进行 long <=> double 转换,存储的时候使用 long 型,计算的时候转化为 double 型。)

参考资料

谈谈ConcurrentHashMap1.7和1.8的不同实现

jdk1.8 LongAdder源码学习

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/69085.html

相关文章

  • Java多线程进阶(十七)—— J.U.C之atomic框架:LongAdder

    摘要:在并发量较低的环境下,线程冲突的概率比较小,自旋的次数不会很多。比如有三个,每个线程对增加。的核心方法还是通过例子来看假设现在有一个对象,四个线程同时对进行累加操作。 showImg(https://segmentfault.com/img/remote/1460000016012084); 本文首发于一世流云的专栏:https://segmentfault.com/blog... ...

    fengxiuping 评论0 收藏0
  • 还在用Synchronized?Atomic你了解不?

    摘要:失败重试自旋比如说,我上面用了个线程,对值进行加。我们都知道如果在线程安全的情况下,这个值最终的结果一定是为的。那就意味着每个线程都会对这个值实质地进行加。 前言 只有光头才能变强 之前已经写过多线程相关的文章了,有兴趣的同学可以去了解一下: https://github.com/ZhongFuCheng3y/3y/blob/master/src/thread.md showImg(h...

    陈江龙 评论0 收藏0
  • AtomicInteger 原子类的作用

    摘要:原子类的作用多线程操作,性能开销太大并不是原子操作。每次比较的是两个对象性能比要好使用时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的会成功,所以其他线程会不断尝试自旋尝试操作,这会浪费不少的资源。 AtomicInteger 原子类的作用 多线程操作,Synchronized 性能开销太大count++并不是原子操作。因为count++需要经过读取-...

    MartinDai 评论0 收藏0
  • Java 8 并发: 原子变量和 ConcurrentMap

    摘要:在有些情况下,原子操作可以在不使用关键字和锁的情况下解决多线程安全问题。但其内部的结果不是一个单一的值这个类的内部维护了一组变量来减少多线程的争用。当来自多线程的更新比读取更频繁时这个类往往优于其他的原子类。 原文地址: Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap AtomicInteger java...

    yy13818512006 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<