LongAdder源码解析

本文详细解读了LongAdder的核心源码,涉及Cell数组操作、cas操作、随机探查和冲突解决机制。重点讲解了getProbe()的作用和数组定义为2^n的原因。深入理解了如何通过cas和再hash实现高并发计数器的线程安全增长。
摘要由CSDN通过智能技术生成

LongAdder核心源码如下

 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[] as:暂存Cell数组

long b:暂存base的值

long v:暂存a的值

int m:暂存as数组的长度

Cell a:根据getProbe()与Cell数组的长度做&操作得到数组中的一个Cell,这里留个疑问,getProbe()是干啥的?

1、首先判断cells数组不为空,或者对base值做cas操作失败,则进入if的逻辑。

2、第二个if中判断as为空,或者随机的Cell为空,或者对随机的Cell做cas操作失败,则进入longAccumulate方法,这里需要注意一点,我们可以看到getProbe()获取随机数的方法与cell数组的长度m做的是&操作,通过以往的源码经验可以知道,这个m必定是2^{n}-1,我们可以带着这个疑问进入longAccumulate方法。

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
        int h;
        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;
            //cells数组不为空
            if ((as = cells) != null && (n = as.length) > 0) {
                //如果取到的Cell为空,则初始化Cell
                if ((a = as[(n - 1) & h]) == null) {
                    //先判断cellBusy是否为0,cellBusy这里起到了乐观锁的作用
                    if (cellsBusy == 0) {       // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        //再次判断cellBusy是否为0,并且通过cas给cellBusy加锁
                        if (cellsBusy == 0 && casCellsBusy()) {
                            boolean created = false;
                            try {               // Recheck under lock
                                Cell[] rs; int m, j;
                                //这里再次判断cell数组中对应的数据是否为空,有点单例模式里面双重锁校验的意思在里面,如果不为空,才设置值
                                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;
                }
                //外层传进来的,对cell的cas失败就会是false,这里先置为true,这里对wasUncontended判断就相当于一种拦截,默认如果前面已经发生锁竞争,cas失败,则直接先进行再hash操作
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                //经历了前面的初始化,这里的cell肯定不为空,直接做cas操作,如果成功则跳出循环
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                  //cell数组长度大于NCPU,或者发生了变化,则设置collide为false,直接进行再hash
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                //如果发生了锁竞争,也直接进行再hash
                else if (!collide)
                    collide = true;
                //如果拿到了cellBusy锁且cells数组地址无变化,则扩充cells数组
                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
                }
                //再hash
                h = advanceProbe(h);
            }
            //如果cell为空则先初始化
            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;
            }
            //如果没拿到cellsBusy锁,则还是先更新base
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

3、可以看到longAccumulate方法非常的长。

首先是判断getProbe()是否为0,如果为0就初始化一下,似乎这个值是不能为0的。

具体的可以查看代码上面的注释,大体上就是通过各种cas操作来实现数据递增,cell数组初始化长度为2,可以看到在扩充时是二进制左移一位,即原来的长度乘以2,符合我们前面的猜想。

4、cell数组定义成2^n的原因,定义成2^n后实际的下标就是0-2^n-1,对一个数跟2^n-1进行&的操作就相当于对2^n进行%操作,只是&会快一些,所以我们能看到很多源码长度都是用的2^n。

   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小飞侠fly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值