LongAddr类的源码解析

129 阅读3分钟

LongAddr

LongAddr这个类是jdk1.8才开始有的,这个类的主要作用就是原子递增或者递减,jdk8之前有AtomicLong这个类可以用来计数,那为什么还要有LongAddr呢?我们都知道,AtomicLong是用volatile和cas来保证原子性,当并发特别高时大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的cas操作会成功,这就造成了其他线程竞争失败后,会通过自旋的方式再次尝试,白白浪费CPU资源。

LongAddr数据类型

LongAddr继承自java.util.concurrent.atomic包下的Striped64抽象类,Striped64类维护了三个全局变量。

Cell类型的数组,long类型的base,int类型的cellsBusy。

我们来看看Cell的数据结构:

@sun.misc.Contended

是用来解决伪共享,进行字节填充的。Cell的数据结构和AtomicLong差不多。

LongAddr是怎么解决AtomicLong的问题的呢?

大量线程写LongAddr时会在不同的Cell上累加,读取时会将base和Cell数组中每个Cell的值求和返回。

add方法

public void add(long x) {
	Cell[] as; long b, v; int m; Cell a;
if ((as = cells) != null || !casBase(b = base, b + x)) {//(1)
	boolean uncontended = true;
	if (as == null || (m = as.length - 1) < 0 ||//(2)
			(a = as[getProbe() & m]) == null ||//(3)
			!(uncontended = a.cas(v = a.value, v + x)))//(4)
		longAccumulate(x, null, uncontended);//(5)
}
}
  1. 当没有产生并发时,会在base变量上进行cas
  2. 如果产生并发,会在Cell数组中的Cell上进行cas

longAccumulate方法

/**
*进入到这个方法里面有三种情况
*1. cells数组没有初始化,base更新失败
*2.cells数组初始化,但是要更新的位置的cell没有元素
*3.cells数组初始化,要更新的位置cell元素存在,但是更新失败
*
*/
final void longAccumulate(long x, LongBinaryOperator fn,
                          boolean wasUncontended) {
  int h;
  //强行初始化,初始化当前线程的probe变量赋值给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) {
      //当前cells数组已经初始化,但是要更改的位置的cell没有元素,
      //对应上述条件2
      if ((a = as[(n - 1) & h]) == null) {
        //如果现在没有其他线程在修改或者创建cells,
        //或者其他线程在创建Cell
        //尝试创建一个Cell,值为x,放入计算出来的位置,如果成功,返回
        //如果失败,自旋重试
        //如果有其他线程正在操作,重试
        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;
      }
      //如果cells初始化,并且要更改的位置的cell元素存在,
      //先尝试在此位置的cell上操作,如果失败。
      //判断cells的元素数量是不是大于CPU数量或者是不是已经扩容了
      //如果两个条件都不满足,则标识已经发生了冲突,重新hash位置
      //新位置上如果也有元素,并且更新失败,这时就需要扩容了,进行两倍扩容
      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);
    }
    //如果cells未初始化,base更新失败,对应上述条件1
    // 如果此时没有其他线程在创建或者修改cells,或者在创建Cell
    // 那么创建cells,并且hash出一个位置,将新建的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;
    }
    //如果当时有其他线程在创建或者修改cells,或者创建cell,
    //那么尝试修改base值,成功的话返回,失败自旋。
    else if (casBase(v = base, ((fn == null) ? v + x :
                                fn.applyAsLong(v, x))))
      break;                          // Fall back on using base
  }
}

longAccumulate方法比较复杂,总结下来这个方法就是在Cell数组上进行并发修改,未初始化时初始化Cell数组,产生冲突时扩容Cell数组。其中cellsBusy变量就充当一个锁的作用,保证只有一个线程在初始化Cell数组或者扩容Cell数组或者创建Cell。

上面代码注释上所写的,进入longAccumulate方法有三个条件。

  1. cells数组没有初始化,base更新失败
    1. 如果有其他线程正在操作,那么再次尝试casbase变量,成功返回,失败自旋
    2. 如果没有其他线程正在操作,那么创建Cell数组,初始容量为2,并hash一个位置进行cas
  1. cells数组初始化,但是要更新的位置的cell没有元素
    1. 如果有其他线程正在操作,那么重试
    2. 如果没有其他线程正在操作,创建一个Cell,放入hash的位置,成功返回,失败自旋
  1. cells数组初始化,要更新的位置cell元素存在,但是更新失败
    1. 先尝试在此位置的cell上操作,如果失败。先尝试在此位置的cell上操作,如果失败。判断cells的元素数量是不是大于CPU数量或者是不是已经扩容了,如果两个条件都不满足,则标识已经发生了冲突,重新hash位置,新位置上如果也有元素,并且更新失败,这时就需要扩容了,进行两倍扩容

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;
}

就是Cell数组中每个Cell的值加上base返回。