JUC解析-LongAdder

本文深入剖析了JDK 1.8中LongAdder的工作原理及其实现细节,包括如何通过分散原子更新操作来降低冲突概率,提高多线程环境下的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在JDK1.8的atomic中增加了一个原子操作类LongAddr,与AtomicLong相比,在效率方面有了不小的提升。

实现原理

AtomicLong的原子操作是通过CAS进行更新的,当有冲突的时候,会通过自旋的方式等待原子操作,具体:

  public final long updateAndGet(LongUnaryOperator updateFunction) {
    long prev, next;
    do {
      prev = get();
      next = updateFunction.applyAsLong(prev);
    } while (!compareAndSet(prev, next));
    return next;
  }
复制代码

从上面的源码可以看出,在原子更新冲突比较少的时候,更新的效率还是很快的,直接通过CAS更新,但是由于是对同一个元素进行更新,当更新操作比较频繁的时候,发生的冲突的概率就会增大,效率就会降低,现在问题就变为:

怎样减少发生冲突的概率?

在多线程环境下,线程的行为是不可预测的,因此可以在更新的元素上做文章,在AtomicLong中是通过对一个成员变量private volatile long value进行原子的更新操作的,因此冲突也是围绕该元素的,来看下Doug Lea大神是怎么做的:

  1. 初始化一个volatile long base和一个int [] cell的数组,数组中的元素都初始化为0。
  2. 当线程进行原子操作的时候,优先对base变量CAS操作,如果没有发生冲突,则更新成功,如果发生冲突,则放弃自旋的方案,进行步骤3。
  3. 对线程通过hashcode映射到cell数组中的一个元素中,然后将原子的操作更新到该数组元素中。
  4. 最后通过叠加base和cell中的每个元素,就可以获取到原子操作的数值。

在LongAddr中,将原子更新的操作分散到了基础变量base和一个数组中,这样就降低了发生冲突的概率,提升了更新的效率。

具体实现

LongAddr继承自Striped64,具体的操作是通过Striped64实现,Striped64的成员变量如下:

public class LongAdder extends Striped64 implements Serializable {
  //对应上面的数组
  transient volatile Cell[] cells;
  //基础更新变量
  transient volatile long base;
  //同步对cells进行操作的
  transient volatile int cellsBusy;
复制代码

其中Cell的定义如下:

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

    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long valueOffset;
    static {
      try {
          UNSAFE = sun.misc.Unsafe.getUnsafe();
          Class<?> ak = Cell.class;
          valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
      } catch (Exception e) {
          throw new Error(e);
      }
    }
  }
复制代码

@sun.misc.Contended注解是为了解决伪共享的问题,具体不了解的可以看这里浅谈伪共享

Cell中只是包含了一个cas的原子操作行为,主要是数组中的元素进行原子更新操作。

更新操作
  public void add(long x) {
    Cell[] as;
    long b, v;
    int m;
    Cell a;
    //如果cell不为空,或者cell为空但是caseBase失败
    if ((as = cells) != null || !casBase(b = base, b + x)) {
      boolean uncontended = true;
      //1. cells 为空
      //2. cells不为空,但是cells中对应本线程位置为null
      //3. cells中本线程对应的位置不为空,但是对本位置操作case失败
      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);
      }
  }
复制代码
  1. 如果cells数组为空,则直接对基础变量base执行cas操作casBase,如果casBase执行失败,则说明有冲突,执行步骤3。
  2. 如果cells数组不为空,则此时为了降低冲突的概率,优先对cells数组进行更新操作,直接执行步骤3。
  3. 执行子逻辑判断: 3.1. 如果cells为空,直接执行步骤4。 3.2. 如果cells不为空,但是本线程对应的cells数组中的元素为null,则进行步骤4。 3.3. 如果cells不为空,并且本线程对应的数组中的元素不为null,则对该元素进行cas操作,如果执行成功返回,执行失败则进行步骤4。
  4. 执行更新子逻辑,具体参照源码注解

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
  int h;
  // 初始化Thread的种子和随机数
  if ((h = getProbe()) == 0) {
      ThreadLocalRandom.current(); // force initialization
      h = getProbe();
      wasUncontended = true;
  }
  boolean collide = false;    // True if last slot nonempty
  //步骤6
  for (;;) {
    Cell[] as; Cell a; int n; long v;
    //如果cells数据不为空,即满足条件3.2和3.3时
    if ((as = cells) != null && (n = as.length) > 0) {
      //如果被hash到的cells数组位置为null(对应条件3.2)
      if ((a = as[(n - 1) & h]) == null) {
        //如果此时cells数组中同步锁为空
        if (cellsBusy == 0) {
          //初始化Cell元素,初试化为x
          Cell r = new Cell(x);
          //设置cellsBusy锁,避免其他线程同时修改cells数组
          if (cellsBusy == 0 && casCellsBusy()) {
            boolean created = false;
            try {
              //初始化cells数组中本线程对应的元素
              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锁
              cellsBusy = 0;
            }
            //如果创建成功则退出,否则continue
            if (created)
              break;
            /*如果created为false,说明上面指定的cells数组的位置
            cells[m%cells.length]已经有其它线程设置了cell了,
            继续重新开始循环。*/
            continue; 
          }
        }
        //如果cellsBusy=1,说明有线程正在更改cells数组,
        将collide设置为false
        collide = false;
      }
      /*如果被hash到的cells数组位置不为null(对应3.3),
      说明已经发生竞争,将wasUncontended设置为true,
      最后重新计算一个新的probe,然后重新执行循环。
      */
      else if (!wasUncontended)       
        wasUncontended = true;        
      /*如果当前线程第一次参与cells争用的cas失败,
      这里会尝试将x值加到cells[m%cells.length]的value ,
      如果成功直接退出*/
      else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                  fn.applyAsLong(v, x))))
        break;
      //如果cells数组的长度已经到了最大值(大于等于cup数量),
      或者是当前cells已经做了扩容,则将collide设置为false,
      后面重新计算prob的值.*/
      else if (n >= NCPU || cells != as)
        collide = false; 
      /*如果发生了冲突collide=false,则设置其为true;
      会在最后重新计算hash值后,进入下一次for循环*/
      else if (!collide)
        collide = true;
      /*扩容cells数组,新参与cell争用的线程两次均失败,
      且符合库容条件,会执行该分支*/
      else if (cellsBusy == 0 && casCellsBusy()) {
        try {
            if (cells == as) {      
              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;                   
      }
      //重新计算线程的hash值
      h = advanceProbe(h);
    } 
    //如果满足3.1 和 3.2 则初始化cells数组
    else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
      boolean init = false;
      try {                           
        if (cells == as) {
          Cell[] rs = new Cell[2];
          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;                  
  }
}

复制代码

上面代码写的比较简单,但是逻辑比较绕,主要就是同步的在base和cells数据更新之间操作,降低发生冲突的概率,提高效率。

获取结果

由于在多线程环境下,数据的更新操作是分散到base变量和cells数组中的每个元素中的,因此每次计算结果都要把全部的数值叠加起来

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;
}
复制代码
线程hash数值的计算

基本的实现逻辑:

  1. 在Thread线程类中保存有两个变量:
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;  //此线程的随机数

@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed; //此线程的随机种子
复制代码
  1. ThreadLocalRandom类会更新每个线程类种子,并且根据种子计算出该线程的随机数,这样线程之间就不会存在随机数的同步成本,提高效率。
  //判断该线程的随机数是否已经初始化,如果没有则执行localInit初始化
  public static ThreadLocalRandom current() {
    if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
      localInit();
    return instance;
  }
  //更新线程的种子数和随机数
  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);
  }
复制代码
  1. 如果发生冲突,可以对Thread进行rehash,具体如下:
  static final int advanceProbe(int probe) {
    probe ^= probe << 13;   // xorshift
    probe ^= probe >>> 17;
    probe ^= probe << 5;
    UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
    return probe;
  }
复制代码

完。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值