JDK8之LongAdder详解

640?wx_fmt=gif

 01

该类的定义

public class LongAdder extends Striped64 implements Serializable {	
    private static final long serialVersionUID = 7249069246863182397L;	
    public LongAdder() {	
    }

从上面的LongAdder 的定义结构看该类实现了Striped64 抽象类。那么它具体有哪些方法呢?下面来看下图:

640?wx_fmt=png

从这些方法中我们看到LongAdder中没有类似于AtomicLong中getAndIncrement()或者incrementAndGet()这样的原子操作。那么它又是怎样保证原子性及并发操作的呢?

 02

方法详解

Striped64类内部重要的成员变量:

   //用于存放cell的hash表,大小是2的幂次方	
    transient volatile Cell[] cells;	
  	
   //基础值	
   //1. 在没有竞争时会更新这个值;	
   //2. 在cells初始化的过程中,cells处于不可用的状态,这时候也会尝试将通过cas操作值累加到base。	
    transient volatile long base;	
    //自旋锁,通过CAS操作加锁,用于保护创建或者扩展Cell表。	
    transient volatile int cellsBusy;

成员变量cells

cells数组是LongAdder高性能实现的必杀器: 

AtomicInteger只有一个value,所有线程累加都要通过cas竞争value这一个变量,高并发下线程争用非常严重; 

而LongAdder则有两个值用于累加,一个是base,它的作用类似于AtomicInteger里面的value,在没有竞争的情况不会用到cells数组,它为null,这时使用base做累加,有了竞争后cells数组就上场了,第一次初始化长度为2,以后每次扩容都是变为原来的两倍,直到cells数组的长度大于等于当前服务器cpu的数量为止就不在扩容;每个线程会通过线程对cells[threadLocalRandomProbe%cells.length]位置的Cell对象中的value做累加,这样相当于将线程绑定到了cells中的某个cell对象上;

成员变量cellsBusy

cellsBusy,它有两个值0 或1,它的作用是当要修改cells数组时加锁,防止多线程同时修改cells数组,0为无锁,1为加锁,加锁的状况有三种 

1. cells数组初始化的时候; 

2. cells数组扩容的时候; 

3. 如果cells数组中某个元素为null,给这个位置创建新的Cell对象的时候;

成员变量base

它有两个作用: 

1. 在开始没有竞争的情况下,将累加值累加到base 

2. 在cells初始化的过程中,cells不可用,这时会尝试将值累加到base上;

add方法详解:

public void add(long x) {	
        Cell[] as; long b, v; int m; Cell a;	
        /**	
         * 1. cells数组不为null(不存在争用的时候,cells数组一定为null,一旦对base的cas操作失败,才会初始化cells数组)	
         * 2. 如果cells数组为null,如果casBase执行成功,则直接返回,如果casBase方法执行失败(casBase失败,说明第一次争用冲突产生,需要对cells数组初始化)进入if内;	
         * casBase方法很简单,就是通过UNSAFE类的cas设置成员变量base的值为base+要累加的值	
         * casBase执行成功的前提是无竞争,这时候cells数组还没有用到为null,可见在无竞争的情况下是类似于AtomticInteger处理方式,使用cas做累加。	
         */	
        if ((as = cells) != null || !casBase(b = base, b + x)) {	
            //uncontended判断cells数组中,当前线程要做cas累加操作的某个元素是否#不#存在争用,如果cas失败则存在争用;uncontended=false代表存在争用,uncontended=true代表不存在争用。	
 	
            boolean uncontended = true;	
            /**	
            *1. as == null :cells数组未被初始化,成立则直接进入if执行cell初始化	
            *2. (m = as.length - 1) < 0:cells数组的长度为0	
            *条件1与2都代表cells数组没有被初始化成功,初始化成功的cells数组长度为2;	
            *3. (a = as[getProbe() & m]) == null :如果cells被初始化,且它的长度不为0,则通过getProbe方法获取当前线程Thread的threadLocalRandomProbe变量的值,初始为0,然后执行threadLocalRandomProbe&(cells.length-1 ),相当于m%cells.length;如果cells[threadLocalRandomProbe%cells.length]的位置为null,这说明这个位置从来没有线程做过累加,需要进入if继续执行,在这个位置创建一个新的Cell对象;	
            *4. !(uncontended = a.cas(v = a.value, v + x)):尝试对cells[threadLocalRandomProbe%cells.length]位置的Cell对象中的value值做累加操作,并返回操作结果,如果失败了则进入if,重新计算一个threadLocalRandomProbe;	
            如果进入if语句执行longAccumulate方法,有三种情况	
            1. 前两个条件代表cells没有初始化,	
            2. 第三个条件指当前线程hash到的cells数组中的位置还没有其它线程做过累加操作,	
            3. 第四个条件代表产生了冲突,uncontended=false	
            **/	
            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);	
        }	
    }	
 

longAccumulate方法详解:

根据上面方法调用可以看出有三个参数,三个参数第一个为要累加的值,第二个为null,第三个为wasUncontended表示调用方法之前的add方法是否未发生竞争;

final void longAccumulate(long x, LongBinaryOperator fn,boolean wasUncontended) {	
        //获取当前线程的threadLocalRandomProbe值作为hash值,如果当前线程的threadLocalRandomProbe为0,说明当前线程是第一次进入该方法,则强制设置线程的threadLocalRandomProbe为ThreadLocalRandom类的成员静态私有变量probeGenerator的值,后面会详细说hash值的生成;	
        //另外需要注意,如果threadLocalRandomProbe=0,代表新的线程开始参与cell争用的情况	
        //1.当前线程之前还没有参与过cells争用(也许cells数组还没初始化,进到当前方法来就是为了初始化cells数组后争用的),是第一次执行base的cas累加操作失败;	
        //2.或者是在执行add方法时,对cells某个位置的Cell的cas操作第一次失败,则将wasUncontended设置为false,那么这里会将其重新置为true;第一次执行操作失败;	
       //凡是参与了cell争用操作的线程threadLocalRandomProbe都不为0;	
        int h;	
        if ((h = getProbe()) == 0) {	
            //初始化ThreadLocalRandom;	
            ThreadLocalRandom.current(); // force initialization	
            //将h设置为0x9e3779b9	
            h = getProbe();	
            //设置未竞争标记为true	
            wasUncontended = true;	
        }	
        //cas冲突标志,表示当前线程hash到的Cells数组的位置,做cas累加操作时与其它线程发生了冲突,cas失败;collide=true代表有冲突,collide=false代表无冲突 	
        boolean collide = false; 	
        for (;;) {	
            Cell[] as; Cell a; int n; long v;	
            //这个主干if有三个分支	
            //1.主分支一:处理cells数组已经正常初始化了的情况(这个if分支处理add方法的四个条件中的3和4)	
            //2.主分支二:处理cells数组没有初始化或者长度为0的情况;(这个分支处理add方法的四个条件中的1和2)	
            //3.主分支三:处理如果cell数组没有初始化,并且其它线程正在执行对cells数组初始化的操作,及cellbusy=1;则尝试将累加值通过cas累加到base上	
            //先看主分支一	
            if ((as = cells) != null && (n = as.length) > 0) {	
                /**	
                 *内部小分支一:这个是处理add方法内部if分支的条件3:如果被hash到的位置为null,说明没有线程在这个位置设置过值,没有竞争,可以直接使用,则用x值作为初始值创建一个新的Cell对象,对cells数组使用cellsBusy加锁,然后将这个Cell对象放到cells[m%cells.length]位置上 	
                 */	
                if ((a = as[(n - 1) & h]) == null) {	
                    //cellsBusy == 0 代表当前没有线程cells数组做修改	
                    if (cellsBusy == 0) {	
                        //将要累加的x值作为初始值创建一个新的Cell对象,	
                        Cell r = new Cell(x); 	
                        //如果cellsBusy=0无锁,则通过cas将cellsBusy设置为1加锁	
                        if (cellsBusy == 0 && casCellsBusy()) {	
                            //标记Cell是否创建成功并放入到cells数组被hash的位置上	
                            boolean created = false;	
                            try {	
                                Cell[] rs; int m, j;	
                                //再次检查cells数组不为null,且长度不为空,且hash到的位置的Cell为null	
                                if ((rs = cells) != null &&	
                                    (m = rs.length) > 0 &&	
                                    rs[j = (m - 1) & h] == null) {	
                                    //将新的cell设置到该位置	
                                    rs[j] = r;	
                                    created = true;	
                                }	
                            } finally {	
                                //去掉锁	
                                cellsBusy = 0;	
                            }	
                            //生成成功,跳出循环	
                            if (created)	
                                break;	
                            //如果created为false,说明上面指定的cells数组的位置cells[m%cells.length]已经有其它线程设置了cell了,继续执行循环。	
                            continue;	
                        }	
                    }	
                   //如果执行的当前行,代表cellsBusy=1,有线程正在更改cells数组,代表产生了冲突,将collide设置为false	
                    collide = false;	
 	
                /**	
                 *内部小分支二:如果add方法中条件4的通过cas设置cells[m%cells.length]位置的Cell对象中的value值设置为v+x失败,说明已经发生竞争,将wasUncontended设置为true,跳出内部的if判断,最后重新计算一个新的probe,然后重新执行循环;	
                 */	
                } else if (!wasUncontended)  	
                    //设置未竞争标志位true,继续执行,后面会算一个新的probe值,然后重新执行循环。	
                    wasUncontended = true;	
                /**	
                *内部小分支三:新的争用线程参与争用的情况:处理刚进入当前方法时threadLocalRandomProbe=0的情况,也就是当前线程第一次参与cell争用的cas失败,这里会尝试将x值加到cells[m%cells.length]的value ,如果成功直接退出  	
                */	
                else if (a.cas(v = a.value, ((fn == null) ? v + x :	
                                             fn.applyAsLong(v, x))))	
                    break;	
                /**	
                 *内部小分支四:分支3处理新的线程争用执行失败了,这时如果cells数组的长度已经到了最大值(大于等于cup数量),或者是当前cells已经做了扩容,则将collide设置为false,后面重新计算prob的值	
                else if (n >= NCPU || cells != as)	
                    collide = false;	
                /**	
                 *内部小分支五:如果发生了冲突collide=false,则设置其为true;会在最后重新计算hash值后,进入下一次for循环	
                 */	
                else if (!collide)	
                    //设置冲突标志,表示发生了冲突,需要再次生成hash,重试。如果下次重试任然走到了改分支此时collide=true,!collide条件不成立,则走后一个分支	
                    collide = true;	
                /**	
                 *内部小分支六:扩容cells数组,新参与cell争用的线程两次均失败,且符合扩容条件,会执行该分支	
                 */	
                else if (cellsBusy == 0 && casCellsBusy()) {	
                    try {	
                        //检查cells是否已经被扩容	
                        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);	
 	
            //这个大的分支处理add方法中的条件1与条件2成立的情况,如果cell表还未初始化或者长度为0,先尝试获取cellsBusy锁。	
            }else if (cellsBusy == 0 && cells == as && casCellsBusy()) {	
                boolean init = false;	
                try {                           // Initialize table	
                    //初始化cells数组,初始容量为2,并将x值通过hash&1,放到0个或第1个位置上	
                    if (cells == as) {	
                        Cell[] rs = new Cell[2];	
                        rs[h & 1] = new Cell(x);	
                        cells = rs;	
                        init = true;	
                    }	
                } finally {	
                    //解锁	
                    cellsBusy = 0;	
                }	
                //如果init为true说明初始化成功,跳出循环	
                if (init)	
                    break;	
            }	
            /**	
             *如果以上操作都失败了,则尝试将值累加到base上;	
             */	
            else if (casBase(v = base, ((fn == null) ? v + x :	
                                        fn.applyAsLong(v, x))))	
                break;                  // Fall back on using base	
        }	
    }	
 

hash的生成详解:

hash是LongAdder定位当前线程应该将值累加到cells数组哪个位置上的,所以hash的算法是非常重要的,下面就来看看它的实现:

java的Thread类里面有一个成员变量

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */	
    @sun.misc.Contended("tlr")	
    int threadLocalRandomProbe;

threadLocalRandomProbe这个变量的值就是LongAdder用来hash定位Cells数组位置的,平时线程的这个变量一般用不到,它的值一直都是0。

在LongAdder的父类Striped64里通过getProbe方法获取当前线程threadLocalRandomProbe的值:


	
    /**	
     * PROBE是threadLocalRandomProbe变量在Thread类里面的偏移量,所以下面语句获取的就是threadLocalRandomProbe的值;	
     */	
    static final int getProbe() {	
        return UNSAFE.getInt(Thread.currentThread(), PROBE);	
    }	

threadLocalRandomProbe的初始化线程对LongAdder的累加操作,在没有进入longAccumulate方法前,threadLocalRandomProbe一直都是0,当发生争用后才会进入longAccumulate方法中。

//进入方法的第一步操作	
int h;	
        if ((h = getProbe()) == 0) {	
            ThreadLocalRandom.current(); // force initialization	
            h = getProbe();	
            wasUncontended = true;	
        }

ThreadLocalRandom.current();方法内部实现:

   /**	
     * Returns the current thread's {@code ThreadLocalRandom}.	
     *	
     * @return the current thread's {@code ThreadLocalRandom}	
     */	
    public static ThreadLocalRandom current() {	
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)	
            localInit();	
        return instance;	
    }	

从该方法看出判断了probe的值是否为0,又调用了localInit()方法:

    static final void localInit() {	
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);	
        int probe = (p == 0) ? 1 : p; // skip 0	
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));	
        Thread t = Thread.currentThread();	
        UNSAFE.putLong(t, SEED, seed);	
        UNSAFE.putInt(t, PROBE, probe);	
    }

probeGenerator是AtomicInteger类,当每次调用init方法是都将累加一次。

 03

总结

LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作,取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同。不得不说这种分布式的设计还是很巧妙的。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值