LongAddr原子累加器源码详解

1,927 阅读7分钟

本文只针对原子累加器的加法API进行分析,正所谓万变不离其宗,笔者昨日在观看教学视频后,忽有所悟,因而在此刻撰下一篇LongAddr的源码解析文章,供码界前辈后进一同阅览。

为什么要引入LongAddr

JUC包下,大师Doug Lea已经发明了原子类AtomicLong自然也可以用来做累加,那么为什么还要引入LongAddr呢?让我们来简单看一看AtomicLong的具体实现。 很显然,AtomicLong使用了Unsafe包下的CAS操作来实现替换的,然而,CAS在多线程竞争的情况下效率并不高,大量的线程占据CPU资源后就会发生空转。 因此,为了提高效率就引入LongAddr

LongAddr的核心

LongAdder维护了一个延迟初始化的原子性更新数组(默认情况下Cell数组是null)和一个基值变量base。由于Cells占用的内存是相对比较大的,所以一开始并不创建它,而是在需要时创建,也就是惰性加载。

Cell

想要了解LongAddr首先就要了解它的field,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);
            }
        }
    }

@Contended注解是用来防止伪缓存行共享的,一个缓存行的长度是64byte,而一个Cell的大小是24byte(对象头16byte+属性-value-8byte)。Cell本质上也是使用Unsafe包下的CAS操作来完成线程安全的操作(比如++操作)

NCPU

CAS操作本质上是一种自旋盲循环,自旋盲循环是要消耗CPU资源的,而availableProcessors()这个方法就可以获取JVM的可用核心数。

Cells

Cell数组,这是LongAddr提升性能的关键

base

在没有竞争的时候,就更新这个值

cellsBusy

初始化或者扩容时的CAS标志位

LongAddr的increment

add

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);
        }
    }

要搞清楚一个方法做什么,我们应该用解构的思想来看待它,那么在这个add方法中,它一共有5个临时变量,两个成员变量(LongAddr的父类)

第一个if

不得不说,第一个if中的逻辑极其巧妙,短短一行代码中就蕴含了极其丰富的逻辑,值得我们细细研究。

  • (as = cells) != null || !casBase(b = base,b+x) 由于cells需要的内存比较大,所以是惰性加载的,那么在第一次累加的时候,as!=null一定是false但这不意味着后续的cas操作一定能成功假设有线程A、B,同时累加,而由于cells是惰性加载的,那cells此刻一定是null,就执行到if语句的后半段,那么很显然A、B线程中只有一个线程会执行cas操作成功。那么那个不成功的就会进入下个阶段的判断
  • as == null || (m = as.length - 1)
  • a = as[getProbe() & m]) == null 这两行判断决定了当前线程应该访问cells数组里面的哪一个Cell元素,如果当前线程映射的的Cell元素存在,就执行代码
  • !(uncontended = a.cas(v = a.value, v + x)) 这行代码显然是使用cas操作去更新当前线程对应的cells数组中Cell元素的value。 如果当前线程映射的Cell元素不存在,或者cas操作失败,那么就执行这行代码
  • longAccumulate(x, null, uncontended);

在分析longAccumulate方法之前,我们可以归纳一下在add方法中这些临时变量有什么用,as代表了cells数组,m->cells数组的长度-1,b->base,a->Cell(cells数组中当前线程对应的Cell元素) v->cells数组中当前线程对应的Cell元素的值。此外,当前线程访问cells中的哪一个Cell元素由getProbe() & m来决定,我们已经知道m代表数组的长度-1,那么getProbe方法是获取当前线程中threadLocalRandomProbe的值,这个值一开始为0,在longAccumuulate方法中会进行初始化,并且当前线程通过分配到Cell元素的cas操作来保证value更新的原子性。

longAccumulate

这个方法是LongAddr的核心,逻辑极其复杂

 final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        //这里是在对当前线程的Probe的值进行初始化
        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);
            }
            // cells 数组的初始化
            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
        }
    }

由于此处代码极其复杂,笔者只能尽自己所能将代码逻辑说得通顺一些,但是决计做不到尽善尽美,所以看待笔者对代码的分析的时候,一定要本着怀疑批判的逻辑来阅读,否则因为笔者的一些疏忽而造成误人子弟的后果,反而就不美了。话说回来,前辈后进们若是发现有不美之处,欢迎斧正指出。

cells 数组的初始化

 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;
            }

casCellsBusy方法,通过cas操作将cellBusy设置为1代表cells正在经历初始化或者扩容,当然在上面这块代码中,cells正在初始化

init标志位,一开始设置为false,代表cells还没有初始化,初始化完成后,将cellsBusy设置为0,可以看做一种解锁操作,最后根据init代表初始化完成,从而退出循环。

增加一个cell元素

  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);
            }

这块代码的语境应该是,某一个线程计算出的probe在cells数组中为null或者说probe大于当前cells的数组长度,那么就要添加往cells数组中添加Cell元素,这个操作是需要上锁的,cas乐观锁

 else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;

这段代码在使用cas操作对当前线程对应的Cell元素进行value的替换,如果成功就退出循环。

  else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale

前文已经提过,cas操作的最佳性能只有在执行cas操作的线程数和CPU数相等的时候才能达到,所以才会有这个判断,因此cells数组的长度取决于CPU的数量,(准确地来说,是JVM可用核心数)

 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
                }

这一块代码是在执行扩容操作,和初始化、以及添加Cell元素一样,扩容操作同样需要加上CAS锁。

 h = advanceProbe(h);

这是重新计算hash值,该计算发生在当前线程计算出的偏移量在cells数组中对应的Cell在有竞争的情况下就要的重新计算hash值,寻找一个空闲的Cell