面试准备--原子类 LongAdder 详解

前面介绍了 AtomicInteger 后,就没有继续介绍 AtomicLong,因为这两个类基本是类似的,懂了第一个那第二个就不在话下了。

根据前面学习过 AtomicInteger 代码,我们可以知道类内部有一个变量 value 专门保存当前的值。然后在增加或减少指定数值时,会不断 CAS 直到成功。

那这样的话,value 作为一个热点值,会被频繁的修改。如果并发量低的情况下,CAS 失败的次数较少,性能可以杠杠的。但是在高并发环境下,N 条线程一起来执行,这时,线程不断的自旋尝试修改,造成大量的 CAS 失败,在这种不断失败的情况下,就造成了性能瓶颈。

那有没有减少线程冲突,提升性能的方案呢?别说,还真有这种方案。LongAdder 类就是为减少线程间冲突,提升性能而出现的方案。具体是使用分段的方案,根据 CPU 核数进行确定具体分为几个槽,每条线程会命中一个的槽,然后在自己的槽中 CAS 修改变量 value。

这样做,可以有效的将 AtomicInteger 中热点变量 value 从一个分为多个,有效的减少了线程的冲突,性能也相对应得到提升。

举个例子:假设我们开了一个快餐店(AtomicInteger),生意火爆,营业额(value)在不断的变化。生意火爆,很多人都买不到快餐。这时想到一个开分店的方式,就开了 3 家分店来分流(LongAdder),每家分店都有自己的营业额(value),每天晚上进行一天的营业额汇总,即可知道今天赚了多少。

介绍基本原理,我们来学习一下 LongAdder 是如何实现的。

相关类图:
在这里插入图片描述
从上图,我们可以看出 LongAdder 继承自 Striped64 这个抽象类,所有也有会 Number 中数据转型的方法。

我们先看看父类 Striped64 :

内部类 Cell 内部有字段 value,该值和 AtomicInteger 的 value 一样会被多条线程竞争修改。

@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);
        }
        //以下省略
    }

重要字段

 	/** CPU 核数 */
    static final int NCPU = Runtime.getRuntime().availableProcessors();
    /**
     * 这就是槽,一般初始化大小是 2 的幂次方
     */
    transient volatile Cell[] cells;
    /**
     * 这个很重要,如果没有发生线程冲突的情况下,直接在该值
     * 原基础上加,发生竞争则在槽中 value 修改
     */
    transient volatile long base;
    /**
     * 这时一个锁,当槽初始化或扩容时,会锁住当前容器
     */
    transient volatile int cellsBusy;

因为子类又调用了父类的方法,所有我们先看看子类的核心方法

LongAdder 类的核心方法:

	/**
	 * 增加多少
	 */
	public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        /**
         * cells 为空时,说明数组还未初始化
         * casBase(b = base, b + x) 是指存在线程冲突
         */
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            /**判断数组有没被初始化过
             * 初始化过之后直接针对 Cell 进行 CAS
             */
            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);
        }
    }

父类 Striped64 的核心方法

方法被定义成了 final 。

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;
            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;
                            }
                            //created 为true 则为成功,跳出循环
                            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);
            }
            //槽的初始化,cellsBusy == 0 是将槽加锁
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
                boolean init = false;
                try {                           // Initialize table
                    if (cells == as) {
                    	//构建的数组必须是 2 的幂次方
                        Cell[] rs = new Cell[2];
                        //将当前值 X 放入槽中替换某个 value 
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            //槽还在初始化过程中,这里尝试对 base 变量修改
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }

上面方法很长,可能很多人看的就头晕。其实父类中的核心方法 longAccumulate 是考虑了各种情况,比如:槽正在初始化过程,槽的扩容等。

注意:
base 变量在没有线程冲突的情况下是在他基础上累加的,还有一种情况就是线程冲突厉害,槽还在初始化过程中,也会尝试对 base 变量进行修改。

最后,需要将各个槽中的数值和 base 中的数值加起来得到它们的和,方法如下:

	public long sum() {
        Cell[] as = cells; Cell a;
        long sum = base;
        if (as != null) {
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }

求和方法很简单,就是一个循环,long sum = base; 在线程没有冲突时,是直接在该变量值基础上进行增加或减少操作的,所以将各个槽的值累加的情况下,也需要将 base 的数值加入才可以获得准确的返回。

最后:
DoubleAdder 和 LongAdder 的方法基本类似,只是多了一个转型的方法,其他都一样,就不在介绍那个类了。LongAdder 是在 AtomicLong 的优化版本吧,但是也无法完全替代 AtomicLong 的用途。不过从 LongAdder 上我们可以学习到以空间换时间的思路,也算是收获满满啦。

有兴趣的同学欢迎关注公众号
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值