【JUC源码】第一篇LongAdder

JUC

java并发编程,为了解决线程安全问题,出现了syschronized同步方法,为了减小临界区,出现了syschronized同步代码块,为了再次减少临界区出现了原子类,原子类底层使用的是cas操作,cas操作在并发量小的时候效率可以,在并发量大的时候,只有一个线程cas操作成功,其他的线程都在cas空自旋,为了提高cas操作的效率,有两个方案,一个是分散热点,一个是使用队列削峰,分散热点的主要应用就是LondAdder,队列削峰的主要应用是AQS(抽线队列同步器)

LongAdder

是什么

LongAdder是一个分散热点解决方案。继承了Striped64,所有核心操作都位于Striped64中。

image-20220425101144507

Striped64

基本的属性与API

abstract class Striped64 extends Number { 
    @sun.misc.Contended static final class Cell {
        volatile long value;
        Cell(long x) { value = x; }
        //Cell底层使用仍然是cas操作
        final boolean cas(long cmp, long val) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
        }

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

    //CPU核心数
    static final int NCPU = Runtime.getRuntime().availableProcessors();

    //存放Cell的哈希表,大小是2的幂
    //第一次创建的时候是2,以后每次扩容都是2的倍数
    transient volatile Cell[] cells;

    /**
     * 1、没有竞争的时候,会直接更新base
     * 2、在cells正在初始化时,cells不可用,也会尝试将cas操作累加到base
     */
    transient volatile long base;

    /**
     * 自旋锁,通过cas加锁,在创建或者时扩容时使用
     * 0:表示cells数组没有处于创建,扩容cells阶段。
     * 1:表示cells数组处于正在创建,扩容cells阶段。不能进行cell元素的设置
     */
    transient volatile int cellsBusy;

    Striped64() {
    }

    //cas操做base
    final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

    //cas操作cellBusy
    final boolean casCellsBusy() {
        return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
    }

    //线程hash
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }

    //refresh 当前线程hash
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
}
源码解析

LongAdder#add

    public void add(long x) {
        //as是cells数组的引用
        //b是base的引用
        //v是cell的值
        //m是cells数组的长度
        //a是cell
        Cell[] as; long b, v; int m; Cell a;
        //如果cells数组未初始化,并发量小的时候,直接对base进行cas操作
        //如果cells数组已经初始化或者对base数组进行cas操作的并发量太大,则进入if语句

        //条件1:成立说明数组已经初始化(存在争用),未成立说明数组未初始化(不存在争用)
        //条件2:
        // 条件1不成立,即cells数组未初始化,则来到条件2
        // 尝试对base进行cas操作
        // 成立说明cas操作失败,未成立说明cas操作成功
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            //来到这里可能是cells数组已经初始化 或者 是cas操作失败(需要初始化cells且对cell执行cas操作)
            boolean uncontended = true;//设置当前未发生竞争
            if (as == null || (m = as.length - 1) < 0 ||//cells数组未初始化,直接进入longAccumulate
                (a = as[getProbe() & m]) == null ||//当前线程对应的cell未初始化(还没有其他线程在同一个未知做过累加操作),直接进入longAccumulate
                !(uncontended = a.cas(v = a.value, v + x)))//cell已经初始化,但是对cell的cas操作失败(cell存在争用),进入longAccumulate【uncontended=false,发生竞争】
                //默认情况下uncontended都是true,只有当在槽位a上也发生竞争的时候才会是false
                longAccumulate(x, null, uncontended);
        }
    }
核心longAccumulate方法

Striped64#longAccumulate

//牢记这个方法的核心目的:是将数据写到cell上,写完成了就退出自旋。但是小部分可能是写到base(只有在cells数组被其他线程初始化了,当前线程就casBase)

    //x:参与累加的值
    //wasUncontended:是否在cell上发生没有cas竞争,
    //默认为true表示没有发生cas竞争
    //false代表发生了cas争用
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;//线程hash
        //条件成立,说明当前线程还未发配hash值
        if ((h = getProbe()) == 0) {
            //给当前线程分配hash值
            ThreadLocalRandom.current(); // force initialization
            //取出当前线程的hash值
            h = getProbe();
            //
            wasUncontended = true;
        }
        //表示扩容意向,false一定不会扩容,true可能会扩容
        boolean collide = false;                // True if last slot nonempty
        //自旋:目的是对cell进行cas操作累加
        for (;;) {
            //as是cells数组的引用
            //a是当前线程命中的cell
            //n是cells数组的长度
            //v是期望值
            Cell[] as; Cell a; int n; long v;

            //Step1:cells数组已经初始化,当前线程应该将数据写入到对应的cell中,[cell可能已经初始化,也可能未初始化分条件判断]{猜测,继续重试对cells数组cas操作,或者扩容}
            if ((as = cells) != null && (n = as.length) > 0) {
                //来到这里的,要么是命中槽位为空,要么是发生cas竞争
                //Step1.1:获取当前线程所在的槽位cell,且当前的槽位为空
                if ((a = as[(n - 1) & h]) == null) {//命中槽位为空的情况,需要new Cell的过程
                    if (cellsBusy == 0) {       // cells数组处于没有创建,扩容的阶段
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {//修改cellBusy状态为1,表示正在操作cells(还没把数据写到cell,写完在设置为0)
                            boolean created = false;//自旋退出标志,设置cell成功了就退出
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                //条件1,条件2 恒成立
                                //rs[j = (m - 1) & h] == null,为了防止其他线程初始化过该位置,导致丢失数据
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
                                    rs[j] = r;//给cells槽位上赋值
                                    created = true;//赋值成功,设置标志,退出循环
                                }
                            } finally {
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                //前置条件:当前线程所在的cells槽位不为空,cell已经初始化
                //Step1.2:在add方法中发生了竞争的情况
                //进来这个方法,默认情况下uncontended都是true,只有当在槽位a上也发生竞争的时候才会是false
                else if (!wasUncontended)       // CAS already known to fail
                    //设置未发生竞争,再次自旋重置线程hash
                    wasUncontended = true;      // Continue after rehash
                //Step1.3:当前线程重置hash值,然后新命中的槽位不为空,重试cas操作,重试1次;rehash之后,如果得到的槽位还是有值,则再次来这里,重试第2次;
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;//cas成功了退出自旋

                //当前的大if下的主要操作是扩容了

                //前置条件:cas操作失败(两次对cell的cas操作失败,外层的add和现在的Step1.3,原因都是由于cell发生了cas竞争,此时需要扩容cells数组,减少竞争)
                //Step1.4:判断是否需要扩容:
                    //n >= NCPU,当前的cells数组长度已经大于cpu核心,不需要扩容
                    //cells != as true -> 其他线程已经扩容过了,当前线程rehash之后重试即可
                else if (n >= NCPU || cells != as)
                    //扩容意向改为false,表示不扩容了
                    collide = false;            // At max size or stale
                // Step1.5:
                //!collide true -> 扩容意向设置为true,但是不一定扩容,当前celss可能需要扩容
                else if (!collide)
                    collide = true;
                //Step1.6:collide为true,需要扩容。
                //判断cells数组没有上锁,cas操作给cells数组上锁
                else if (cellsBusy == 0 && casCellsBusy()) {
                    //扩展cells数组
                    try {
                        //两个线程同时道道Step1.6,其中一个拿到锁,扩容结束后
                        //另外一个进入扩容,如果不加判断,则会覆盖丢失数据,所以需要cells == as
                        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;
                    //退出本层循环,重新开始Step1,获取新的下标,重新对槽位赋值
                    continue;                   // Retry with expanded table
                }
                //扩容结束或之前发生竞争,重置当前线程的hash值
                h = advanceProbe(h);
            }


            //下面的主要内容是初始化cells,并且累加数据

            //前置条件:cells数组未初始化。应该对cells数组初始化,并且将线程的数据写到cell上
            //Step2:cells数组未加锁【cellsBusy:0未加锁,1加锁】,在并发的情况下,如果要对cells数组进行初始化或者扩容需要上锁
            //条件1:表示未对cells上锁
            //条件2;cells == as?可能存在其他线程已经在Step1将cells初始化,从而cells与as不相同了,所以要判断是否cells还是当初的那个cells【多线程问题】
            //如果未加锁,当前的cells引用与cells相同【有点向cas】,则调用casCellsBusy将cellsBusy设置为1,加上锁
            //条件3:返回true,表示获取锁成功,cellsBusy 修改为1,表示cells上锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    //Step2.1:
                    //cells == as?防止其他线程已经初始化了,再次初始化,丢失数据
                    //可能存在两个线程一起都执行完cellsBusy == 0 && cells == as,其中一个让出了cpu,另一个得到锁,执行完了下面try-finally,cells被修改
                    //而另外一个线程重新得到了锁,进入了try-finally,此时如果不加判断,会导致cells重新被覆盖
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //前置条件:如果cellsBusy加锁失败,表示其他线程正在初始化 cells,所以当前线程将值累加到base上。
            // 1、当前的cellsBusy处于加锁状态,拿锁失败。当前的线程需要将数据累加到base
            //2、自己进入Step2.1:发现自己拿到的as与最新的cells不同,cells被其他线程修改了。当前的线程需要将数据累加到base(也就是说,如果初始化失败,则会累加到base上)
            //Step3:
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
longAccumulate方法小结
public class MyLongAdder {
    final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        //todo:获取线程hash,线程hash为0则为其分配hash

        //自旋
        for(;;){
            if (cells数组已经初始化){
                if (cell没有初始化){

                }

                来到这里,说明cell已经初始化

                else if(在cell上发生了cas竞争){

                }
                else if(经过refresh hash 来到这里对cell再次进行cas操作){
                    操作成功,退出自旋
                }

                来到这里,说明还是对cell的cas失败,说明得给cells扩容了,降低竞争的可能性

                else if(判断cells 是否 达到 CPU核心数){
                    达到CPU核心数,拒绝再次扩容
                }
                else if (拿到cellsBusy锁,对cells进行操作){
                    对cells进行扩容
                }

                再次cas失败,或者扩容结束,重置当前线程hash。

                if  结束

            }
            else{
                cells数组没有初始化,else中做的就是初始化

                if(拿到cellsBusy锁,没有其他线程初始化了cells数组,对其进行初始化){
                    初始化cells数组
                }

                else if(初始化失败){
                    当前前程将数据累加到base上
                }
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值