AtomicLong与LongAdder的区别

AtomicLong与LongAdder都是线程安全的数值计算类,但LongAdder是jdk1.8才新增的,它是基于AtomicLong做了优化。

在平时使用过程中,推荐用LongAdder。

下面分析一下 两者的实现方式。

1、AtomicLong

首先看一下基本数据结构,截取部分源码:

// unsafe对象
private static final Unsafe U = Unsafe.getUnsafe();
// 预期原值(用作cas)
private static final long VALUE
    = U.objectFieldOffset(AtomicLong.class, "value");
// 初始值
private volatile long value;

可以看到,

  • 基础数据是一个 volatile修饰的value值,作为实际计算的值。volatile保证value的可见性
  • 用了Unsafe对象,主要用来调用unsafe方法
  • 有一个预期原值VALUE,U.objectFieldOffset(AtomicLong.class, "value")来获取。用作CAS比较

 看一下计算方法(unsafe类):

@IntrinsicCandidate
public final long getAndAddLong(Object o, long offset, long delta) {
    long v;
    do {
        // 获取当前值
        v = getLongVolatile(o, offset);
        // offset:预期原值
        // v:当前值(实际的值)
        // v+delta:计算后的结果
        // CAS,如果 offset == v,则表示 没有其他线程修改过,则把 计算后的值 赋值给 v,退出循环
        // 如果 offset != v,表示其他线程改过,继续循环比较
    } while (!weakCompareAndSetLong(o, offset, v, v + delta));
    return v;
}

所以AtomicLong的核心是:

1、volatile保证value的可见性、顺序性

2、cas来保证原子性

 

AtomicLong的问题:

当存在大量线程同时修改 这个AtomicLong对象时,那么CAS方法会返回false,那么大量线程都会循环。

(线程数特别多,同时修改值时)这样会导致:循环次数大大提升,计算成功的效率就降低了,计算时间也变长了。

所以基于这个问题,java有了一个新的线程安全的计算类:LongAdder

 

2、LongAdder

LongAdder的思路是:

  • 如果一个值有很多线程同时计算
  • 那我让各个线程,各自计算各自的值。
  • 所有线程计算完成之后,再把所有计算结果再合并起来
  • 这样就完成了最终的计算结果

1、当大量线程要更新原值时

 

2、各个线程先在各自的区间计算,在合并结果

 

 

 

 

 

首先看下主要的类成员:

/**
 * 单元格表。当非空时,大小是 2 的幂。
 */
transient volatile Cell[] cells;

/**
 * 基值,主要在没有争用时使用,但也可作为表初始化竞赛期间的后备。通过 CAS 更新。
 */
transient volatile long base;

/**
 * 调整大小和/或创建单元格时使用自旋锁(通过 CAS 锁定)。
 */
transient volatile int cellsBusy;

这几个都是 父类:Striped64的成员变量

核心add方法:

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    // 先判断cells是否非空
    // 为空,则走casBase,在base上累加,cas操作,类似atomicLong
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        // 非空(cas失败、开始竞争),则通过cells来求和。if内容
        // 当前线程应该操作哪一个cell的index
        int index = getProbe();
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[index & m]) == null ||
            // cas更新cell的值
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended, index);
    }
}

这里可以看到:

  1. 先判断是否需要分区计算,不需要的话,直接用casBase,通过cas更新原值
  2. 否则就要 分区计算了
  3. 先计算此线程要计算哪一个分区值(cells是一个数组),获取此线程的index(hash),这样保证此线程每一次计算时,都更新 同一个cell
  4. 然后就是核心的longAccumulate方法(真正计算的业务逻辑:cells数组初始化和扩容都在这里,此方法有很多逻辑判断,这里忽略实现细节)
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended, int index) {
        if (index == 0) {
            ThreadLocalRandom.current(); // force initialization
            index = getProbe();
            wasUncontended = true;
        }
        for (boolean collide = false;;) {       // True if last slot nonempty
            Cell[] cs; Cell c; int n; long v;
            if ((cs = cells) != null && (n = cs.length) > 0) {
                if ((c = cs[(n - 1) & index]) == null) {
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & index] == null) {
                                    rs[j] = r;
                                    break;
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (c.cas(v = c.value,
                               (fn == null) ? v + x : fn.applyAsLong(v, x)))
                    break;
                else if (n >= NCPU || cells != cs)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == cs)        // Expand table unless stale
                            cells = Arrays.copyOf(cs, n << 1);
                    } finally {
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                index = advanceProbe(index);
            }
            else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {
                try {                           // Initialize table
                    if (cells == cs) {
                        Cell[] rs = new Cell[2];
                        rs[index & 1] = new Cell(x);
                        cells = rs;
                        break;
                    }
                } finally {
                    cellsBusy = 0;
                }
            }
            // Fall back on using base
            else if (casBase(v = base,
                             (fn == null) ? v + x : fn.applyAsLong(v, x)))
                break;
        }
    }

 

总结:

  • longAddr通过分区的方式,将多线程分成多个区间,各自区间都计算自己的,这样大大减少了线程竞争。
  • 当所有线程计算完了,再合并所有区间的结果,就得到真正的结果。

所以这样表明,longAdder在计算过程中,value值不是强一致的。他只保证了最终一致。

 

 

 

posted @ 2022-04-02 16:38  daibiao123  阅读(243)  评论(0)    收藏  举报