LongAddr源码解析

众所周知,jdk提供了AtomicLong通过cas进行非阻塞的操作,但是他在高并发下,会造成大量的线程竞争失败后进行不断自旋,从而导致资源的大量浪费,所以又推出了LongAddr来克服高并发下的性能瓶颈。AtomicLong的思路是多个线程去竞争一个变量,而LongAddr的思路稍微不一样,就是将单个变量改为多个变量,从而提高并发下的性能

查看LongAddr构造函数,发现他只有一个默认的构造函数且没有定义任何全局变量,但是发现他继承了Striped64类,所以变量应该就在父类上了

java 复制代码
public class LongAdder extends Striped64 implements Serializable {
    private static final long serialVersionUID = 7249069246863182397L;

    /**
     * Creates a new adder with initial sum of zero.
     */
    public LongAdder() {
    }

查看Striped64类

java 复制代码
abstract class Striped64 extends Number {

    //避免伪共享
    @sun.misc.Contended static final class Cell {
        // 保持内存可见
        volatile long value;
        Cell(long x) { value = x; }
        //cas操作,保证原子性
        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);
            }
        }
    }

    /** Number of CPUS, to place bound on table size */
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    /**
     * Table of cells. When non-null, size is a power of 2.
     * 用来进行cas竞争
     */
    transient volatile Cell[] cells;

    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     * 基准变量,如果并发量小的情况,避免初始化Cell数组,而直接base进行cas操作
     */
    transient volatile long base;

    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     */
    transient volatile int cellsBusy;

回到LongAddr,查看最常用的increment方法和decrement方法也是同理,发现调用的是add方法

java 复制代码
    /**
     * Equivalent to {@code add(1)}.
     */
    public void increment() {
        add(1L);
    }
java 复制代码
    /**
     * Base value, used mainly when there is no contention, but also as
     * a fallback during table initialization races. Updated via CAS.
     * 基准变量,如果并发量小的情况,避免初始化Cell数组,而直接base进行cas操作
     */
    transient volatile long base;
    
    /**
     * Adds the given value.
     *
     * @param x the value to add
     */
    public void add(long x) {
        Cell[] as;
        long b, v;
        //当前cell数组长度
        int m;
        Cell a;
        //cell为空,则进行cas操作,类似AtomicLong的自增操作
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            //cell不为空,或者cell为空但自旋增加也失败
            boolean uncontended = true;
            if (//cell等于null或者为空数组
                as == null || (m = as.length - 1) < 0 ||
                //获取当前线程对应的Cell对象,getProbe()是当前线程中的threadLocalRandomProbe & m 
                //计算出当前线程对应的cell数组下标
                (a = as[getProbe() & m]) == null ||
                //有Cell冲突,则执行cas操作,失败则执行longAccumulate,成功则Cell执行自增操作
                !(uncontended = a.cas(v = a.value, v + x)))
                //如果cas操作失败,则说明当前线程对应的cell对象被加锁,则调用longAccumulate寻找新的cell对象
                longAccumulate(x, null, uncontended);
        }
    }

因为LongAddr的Cell数组默认是为空的,首先会判断LongAddr的Cell数组是否为空,如果为空则直接base进行cas操作,主要是为了在线程较少的情况下避免创建Cell数组,减少空间的浪费。之后就是判断Cell是否有冲突,有冲突则对当前线程对应的Cell进行Cas操作,没有冲突则执行Striped64的longAccumulate方法

Striped64.longAccumulate方法逻辑还是比较复杂的

ini 复制代码
 final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        // cell数组的索引
        int h;
        // 获取新的索引值,相当于重新rehash寻找新的cell变量
        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;
            //当前线程的cell数组的长度
            int n;
            long v;
            //cell数组不为null,且cell不是一个空数组
            if ((as = cells) != null && (n = as.length) > 0) {
                //当前线程的cell索引的值为空
                if ((a = as[(n - 1) & h]) == null) {
                    //cellBusy表示当前是否有线程处于初始化的过程,0表示没有线程进行初始化,1表示有线程进行初始化
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        //自旋,竞争cellBusy锁
                        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
                //如果有Cell值已经存在,则对当前的Cell对象进行Cas操作
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                //限制cell数组大小小于等于当前cpu数量
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                else if (!collide)
                    collide = true;
                //当前元素个数没有超过cpu总数,且没有进行初始化或者扩容操作,进行扩容
                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
                }
                //rehash
                h = advanceProbe(h);
            }
            //cell数组为空,则进行初始化
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                        //初始化cell数组
                        Cell[] rs = new Cell[2];
                        //hash
                        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
        }
    }

看着很长,其实逻辑还是比较清晰的,这个方法主要的功能就是Cell的初始化和Cell数组的创建和扩容,而他的几个if分支也就对应了这几个功能,其余的分支就是对当前线程或者线程对应的cell进行cas操作

Cell初始化分支

java 复制代码
     /**
     * Table of cells. When non-null, size is a power of 2.
     * 用来进行cas竞争
     */
    transient volatile Cell[] cells;
    
    /**
     * Spinlock (locked via CAS) used when resizing and/or creating Cells.
     * cellBusy表示当前是否有线程处于初始化的过程,0表示没有线程进行初始化,1表示有线程进行初始化
     */
    transient volatile int cellsBusy;
    
  //Cell数组不为空,则进行Cell初始化
  if ((as = cells) != null && (n = as.length) > 0) {
                //当前线程的cell索引的值为空
                if ((a = as[(n - 1) & h]) == null) {
                  
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        //加锁,自旋竞争cellBusy锁,等于0表示当前没有线程进行Cell初始化操作
                        // casCellsBusy,cas修改为1
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            //Cell初始化
                            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
                        }
                    }
                    //当前Cell不为空,则表示有冲突
                    collide = false;
                }

Cell数组扩容分支

ini 复制代码
//限制cell数组大小小于等于当前cpu数量
else if (n >= NCPU || cells != as)
    collide = false;            // At max size or stale
//如果Cell数组有冲突,且Cell数组大学小于当前CPU的数量,则修改为未冲突,进行扩容
else if (!collide)
    collide = true;
//如没有进行Cell初始化和数组扩容,则加锁,进行数组扩容
else if (cellsBusy == 0 && casCellsBusy()) {
    try {
        if (cells == as) {      // Expand table unless stale
            //每次都按2的倍数扩容
            Cell[] rs = new Cell[n << 1];
            for (int i = 0; i < n; ++i)
                rs[i] = as[i];
            cells = rs;
        }
    } finally {
    //解锁
        cellsBusy = 0;
    }
    //重置collide
    collide = false;
    continue;                   // Retry with expanded table
}

Cell数组初始化

ini 复制代码
//cell数组为空,则进行初始化
else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
    boolean init = false;
    try {                           // Initialize table
        if (cells == as) {
            //初始化cell数组
            Cell[] rs = new Cell[2];
            //hash
            rs[h & 1] = new Cell(x);
            cells = rs;
            init = true;
        }
    } finally {
        //解锁
        cellsBusy = 0;
    }
    if (init)
        break;
}

总的来说,LongAddr的Cell数组最大跟当前的CPU数相同,多线程并发会在Cell数组找寻对应的Cell变量进行cas操作,如果Cas操作失败,则会重新hash,找寻新的未加锁的Cell来进行cas操作,这样就能很大程度的减少线程自旋的时间而LongAddr最后输出的值也就是cell数组的总和

相关推荐
天若有情67332 分钟前
探秘 C++ 计数器类:从基础实现到高级应用
java·开发语言·c++
进击的愤怒38 分钟前
GIM发布新版本了 (附rust CLI制作brew bottle流程)
开发语言·后端·rust
x-cmd44 分钟前
x-cmd install | cargo-selector:优雅管理 Rust 项目二进制与示例,开发体验升级
开发语言·后端·rust·cargo·示例
明天不下雨(牛客同名)1 小时前
介绍一下 MVCC
java·服务器·数据库
春生野草1 小时前
如何用JAVA手写一个Tomcat
java·开发语言·tomcat
小猪咪piggy2 小时前
【JavaEE】(1) 计算机如何工作
java
smileNicky2 小时前
SpringBoot系列之OpenAI API 创建智能博客评论助手
java·spring boot·后端
弥鸿2 小时前
MinIO的安装和使用
java·spring boot·java-ee·springcloud·javaee
丿BAIKAL巛3 小时前
如何使用Java生成pdf报告
java·pdf