LongAdder原理

对于longAdder的应用,也是在看到concurrentHashMap源码中,统计数组中元素个数的时候,看到了Doug Lea写的一个注释,才开始去关注这个类,这里主要记录下自己对该类源码的学习笔记
在这里插入图片描述

longAdder核心思想

通常,在说到这个类的时候,会和atomic中原子类有关系,atomicInteger底层的话,通过unsafe中的native方法进行compareAndSwap方法去进行值的更新,同时,在atomic类中声明的value是volatile修饰,保证了可见性;这个是没问题的,但是有一个问题:如果有多个线程来对一个值进行修改的时候,同一时刻,只会有一个线程去更新成功,其他的线程会不停的cas自旋,这样的话,会消耗CPU的资源;此时:LongAdder就可以出场了,简单而言,longAdder是以空间换时间

在这里插入图片描述
对于atomicLong来说,只有前一个value,如果多个线程对一个字段进行加1,其他线程会不停得自旋,直到当前线程对该值+1成功;
那LongAdder怎么做的呢?在多个线程对同一个变量进行+1的时候,会把对value的压力,分散到一个cell数组中,也就是图中后面的value1、value2…valueN,这样的话,虽然需要额外再维护一个数组,但是可以提高并发量,也就是所谓的以空间换时间

源码

在这里插入图片描述
这是longAdder的继承关系

我们来看对应的add方法

add()

/**
 * Adds the given value.
 *
 * cells是volatile修饰的cell数组
 * base:也是volatile修饰的变量,在尝试+1 的时候,其实就是对base变量进行+1
 * 如果base变量+1失败,就需要将+1的操作,转移到cell数组中的某个节点对应的value
 * 在获取总数的时候,就是 base + cell[0] + cell[1] + ... + cell[n]
 *
 * @param x the value to add
 */
public void add(long x) {
    
    
	Cell[] as; long b, v; int m; Cell a;
	/**
	 * 如果cas设置base的值成功,那就无需进入,直接结束运行
	 * 如果cas设置失败,有可能是并发请求导致失败,就会将该线程需要 + 1的动作,放到cell中执行
	 */
	if ((as = cells) != null || !casBase(b = base, b + x)) {
    
    
		boolean uncontended = true;
		/**
		 * 下面这个if判断,最为重要的是最后一个cas
		 * 如果代码执行到最后一个cas表示,当前cells[i]位置不为null,直接cas设置cells[i]位置的值 +1
		 * 如果成功,结束运行
		 * 否则就进入longAccumulate
		 *
		 * getProbe()是根据当前线程生成的一个值,其实是为了判断当前线程 + 1的动作,需要放在数组中的哪个位置执行
		 */
		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);
	}
}
final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
    
    
	int h;
	/**
	 * 1.如果当前线程生成的值为0,就生成一个非0的hash值
	 */
	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;
		/**
		 * 2.如果cell数组已经初始化了,进入下面的流程进行处理
		 */
		if ((as = cells) != null && (n = as.length) > 0) {
    
    
			/**
			 * 2.1 如果i位置为null
			 * 就调用Cell r = new Cell(x); 在进行一些列的判断之后,会将r赋值到cell数组中的i位置
			 */
			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;
						}
						if (created)
							break;
						continue;           // Slot is now non-empty
					}
				}
				collide = false;
			}
			/**
			 * 2.2
			 */
			else if (!wasUncontended)       // CAS already known to fail
				wasUncontended = true;      // Continue after rehash
			/**
			 * 2.3 对i位置进行+1,代码执行到这里,说明要插入的i位置对应的value不为null,需要通过cas去修改值
			 */
			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;
			/**
			 * 2.4 上面会进行一些判断,暂时没看懂,但是下面的逻辑是扩容,默认扩容为原来的2倍
			 */
			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);
		}
		/**
		 * 3.如果cell数组为null,尚未初始化,就进入到这里进行处理
		 * 默认初始化的cell数组长度为2
		 * 然后将i位置赋值为new Cell(x)
		 */
		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;
		}
		/**
		 * 4.这里应该是当前线程在初始化的时候,有其他线程已经在初始化了,此时会再次尝试对base变量进行+1,如果成功,就break
		 * 如果失败,就再次进入for循环
		 */
		else if (casBase(v = base, ((fn == null) ? v + x :
									fn.applyAsLong(v, x))))
			break;                          // Fall back on using base
	}
}

通过上面的代码可以看到,在并发的对value进行+1的时候,是会将压力分散到cell数组中,cell数组中也有可能会发生并发的修改,在longAccumulate中会进行一系列的判断,最终会通过扩容或者对value进行+1等操作来完成最后的计数

sum()

/**
 * Returns the current sum.  The returned value is <em>NOT</em> an
 * atomic snapshot; invocation in the absence of concurrent
 * updates returns an accurate result, but concurrent updates that
 * occur while the sum is being calculated might not be
 * incorporated.
 * 计算总和
 *  base + cells数组中每个节点的value值
 * @return the sum
 */
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;
}

sum()计算总和的方法就简单了,将value的值和cell数组中,每个位置的值累加即可

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/114452037