LongAdder源码阅读

154 阅读4分钟

LongAdder简介


LongAdder是JDK提供的用于并发计数的一个类,这个类继承自Striped64

Striped64中有两个主要属性base和cell数组

当线程对LongAdder进行写入时,会写入其中的base中

如果有线程竞争,写入base失败,就会创建cell数组,写入对应位置的cell中

Markdown

LongAdder属性


LongAdder的属性均是继承自Striped64,cell类中包装了一个value,用来存储计数的值

		
	static final class Cell {
		
		//cell中的值
        volatile long value;
		
		//构造方法
        Cell(long x) { value = x; }
		
		//用CAS方式更新value
        final boolean cas(long cmp, long val) {
            return U.compareAndSwapLong(this, VALUE, cmp, val);
        }
        final void reset() {
            U.putLongVolatile(this, VALUE, 0L);
        }
        final void reset(long identity) {
            U.putLongVolatile(this, VALUE, identity);
        }

        //Unsafe对象
        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        private static final long VALUE;
		//获取到value对应的偏移量,用于CAS操作
        static {
            try {
                VALUE = U.objectFieldOffset
                    (Cell.class.getDeclaredField("value"));
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
        }
    }

   //cell数组,当线程真正写入时才会初始化
    transient volatile Cell[] cells;

    //线程会通过CAS方式更新该值
    transient volatile long base;

    //一把特殊的锁:用来表示cell数组或者cell是否正在初始化,正在扩容
	//0:表示当前锁空闲
	//1:表示当前锁已经被持有
    transient volatile int cellsBusy;

    /**
     * Package-private default constructor.
     */
    Striped64() {
    }
	

线程的probe值和casCellsBusy()方法


当线程需要把数写入cell数组中时,probe值作用和hash值一样,用来确定线程写入数组的哪个cell

		
	/**
     * CASes the cellsBusy field from 0 to 1 to acquire lock.
     * 通过CAS方式获取锁
     */
    final boolean casCellsBusy() {
        return UNSAFE.compareAndSwapInt(this, CELLSBUSY, 0, 1);
    }

    /**
     * Returns the probe value for the current thread.
     * Duplicated from ThreadLocalRandom because of packaging restrictions.
     *
     * 获取当前线程的probe值
     */
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }

    /**
     * Pseudo-randomly advances and records the given probe value for the
     * given thread.
     * Duplicated from ThreadLocalRandom because of packaging restrictions.
     *
     * 重置当前线程的probe值
     */
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }
	

解析add()


add()方法正是LongAdder用来增加计数的方法

		
	public void add(long x) {
	  
		// as:当前的cell数组  b:当前的base  v:当前线程对应的cell的值  m:当前cell数组的长度减一 a; 当前线程对应的cell
        Cell[] as; long b, v; int m; Cell a;
		
		
		//cell数组是否初始化,没有初始化则更新base值
        if ((as = cells) != null || !casBase(b = base, b + x)) {
			
			//cas更新线程对应的cell的value值是否成功
            boolean uncontended = true;
			
			//当前cell数组是否初始化
            if (as == null || (m = as.length - 1) < 0 ||
			
				//线程对应的cell是否初始化
                (a = as[getProbe() & m]) == null ||
			
				//更新cell中value的值是否成功
                !(uncontended = a.cas(v = a.value, v + x)))
			
			
				// 这个方法下面有解释
                longAccumulate(x, null, uncontended);
        }
    }
	

值得注意的是

	if ((as = cells) != null || !casBase(b = base, b + x))

当cells数组初始化后,线程将不再竞争写入base

解析longAccumulate()


进入此方法的情况:

  • 数组尚未初始化,线程写入base失败

  • cell数组已经初始化,线程对应位置的cell尚未初始化

  • cell已经初始化,CAS操作cell更新value失败

		
	//wasUncontended什么时候为true:cell数组为null,或者cell数组中线程对应的cell为null
	//                        false:线程CAS更新cell的value值失败
	final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
	
		//线程的probe值					  
        int h;
		
		//线程尚未分配probe值
        if ((h = getProbe()) == 0) {
		
			//初始化probe
            ThreadLocalRandom.current(); // force initialization
            h = getProbe();
			
			//为什么是true?
			//线程probe值未分配时为0,肯定时写入cell[0],这是特殊情况,不算竞争
            wasUncontended = true;
        }
		
		//可以理解为是否需要扩容
        boolean collide = false;                
		
		//自旋
        done: for (;;) {
		
			//as:cell数组  a:线程对应的cell  n:cell数组长度  v:更新期望值
            Cell[] as; Cell a; int n; long v;
			
			//情况一:cell数组已经被初始化
            if ((as = cells) != null && (n = as.length) > 0) {
			
				//情况a:线程对应的cell没有初始化,要初始化自己的cell
                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()) {
						
                            try {
									
								//rs:当前cell数组  m:数组长度	j:线程对应的数组下标
                                Cell[] rs; int m, j;
								
								//再次检查线程对应的cell
                                if ((rs = cells) != null &&
                                    (m = rs.length) > 0 &&
                                    rs[j = (m - 1) & h] == null) {
									
									//初始化自己的cell
                                    rs[j] = r;
                                    break done;
                                }
                            } finally {
							
								//释放锁
                                cellsBusy = 0;
                            }
							
							//当上面检查cell不是null时,就跳过,再次自旋
                            continue;           // Slot is now non-empty
                        }
                    }
					
					//目前不扩容
                    collide = false;
                }
				
				//情况b:线程CAS更新cell的value值失败:把wasUncontended置为true
				//后面会重置线程probe值,即让线程更新cell数组另一个cell
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
					
				//情况c:线程CAS更新cell的value值
                else if (a.cas(v = a.value,
                               (fn == null) ? v + x : fn.applyAsLong(v, x)))
                    break;
					
				//情况d:n>=NCPU,cell数组已经达到最大长度,不能再扩容了
						cells != as,当前数组已经扩容过了,不能再扩容了
                else if (n >= NCPU || cells != as)
				
					//扩容置为false,继续自旋尝试更新
                    collide = false;            // At max size or stale
					
				//情况e:上面cell更新失败,并且可以扩容	
                else if (!collide)
                    collide = true;
					
				//情况f:拿锁尝试扩容	
                else if (cellsBusy == 0 && casCellsBusy()) {
                    try {
                        if (cells == as)
							
							//扩容,数组为原来2倍
                            cells = Arrays.copyOf(as, n << 1);
                    } finally {
					
						//释放锁
                        cellsBusy = 0;
                    }
					
					//扩容置为false
                    collide = false;
                    continue;                   // Retry with expanded table
                }
				
				//竞争比较大,重置线程probe值,即让线程更新cell数组另一个cell
                h = advanceProbe(h);
            }
			
			//情况二:cell数组没有初始化,要尝试拿锁并且初始化cell数组
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
			
				//初始化cell数组
                try {                           // Initialize table
                    if (cells == as) {
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        break done;
                    }
                } finally {
				
					//释放锁
                    cellsBusy = 0;
                }
            }
            //情况三:前面都失败了,更新base
            else if (casBase(v = base,
                             (fn == null) ? v + x : fn.applyAsLong(v, x)))
                break done;
        }
    }
	

总结一下:这个方法就是初始化cell数组和数组中的各个cell,并在竞争激烈时扩容

sum()方法


获取计数的值,将base和cell数组中存储的value相加 得到的只是一个大概准确的值

		
	public long sum() {
	
		//as:当前cell数组   
        Cell[] as = cells; Cell a;
		
        long sum = base;
        if (as != null) {
		
			//遍历cell数组
            for (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
				
					//将cell中的value值累加到sum中
                    sum += a.value;
            }
        }
        return sum;
    }