与传统累加器对比,LongAdder 为何如此出众?

LongAdder

在使用AtomicLong时,如果是在高并发场景下去同时竞争修改同一个原子变量,由于内部使用的是CAS,只会有一个线程修改成功,这就造成了大量的线程竞争失败后,通过无限循环来不断的进行CAS操作,白白的浪费了CPU资源,在JDK8中为了解决这种问题,提供了LongAdder来进行原子性递增递减

LongAdder将一个变量分解为多个变量,在内部维护了多个Cell变量,每个Cell变量中有一个初始值为0的long类型变量。当多线程争夺同一个Cell原子变量时如果失败,并不是在当前Cell变量上一直自旋CAS重试,而是会尝试在其他Cell变量上进行CAS尝试,增加了CAS成功的可能性

最终,获取LongAdder的当前值时,会把所有Cell变量的value值累加后再加上base值

java 复制代码
// 真实值为base值与cells数组中所有Cell元素中value值的累加
// 在LongAdder中维护了一个Cell数组,分担对单个变量进行竞争的开销
transient volatile Cell[] cells;
// 基值变量
transient volatile long base;
// 用来实现自旋锁,状态值只有0和1,当创建Cell元素,扩容Cell数组和初始化Cell数组时,使用CAS操作该变量来保证同时只有一个线程可以进行其中一个Cell的操作
transient volatile int cellsBusy;

@sun.misc.Contended static final class Cell {
  			// 每个Cell中有一个初始值为0的long类型变量value
        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);
            }
        }
    }


// 进行加一减一操作
public void add(long x) {
  Cell[] as; long b, v; int m; Cell a;
  // 如果cells为null的话在base的基础上进行累加
  if ((as = cells) != null || !casBase(b = base, b + x)) {
    boolean uncontended = true;
    // 选择其中一个Cell进行操作
    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;
  // 初始化当前线程的变量h,用于计算当前线程应该被分到cells数组中的哪个元素
  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;
    // cells如果为null,说明还没有初始化
    // 不为null,说明已经初始化过了
    if ((as = cells) != null && (n = as.length) > 0) {
      // 数组cells已经扩容,但是有的Cell还没有填充,此时为null,需要新增一个Cell元素到cells数组中
      if ((a = as[(n - 1) & h]) == null) {
        if (cellsBusy == 0) {       // Try to attach new Cell
          Cell r = new Cell(x);   // Optimistically create
          // 添加新元素需要将cellsBusy置为1
          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;
      }
      else if (!wasUncontended)       // CAS already known to fail
        wasUncontended = true;      // Continue after rehash
      // 当前Cell存在,则执行CAS操作
      else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                   fn.applyAsLong(v, x))))
        break;
      // 当前Cell数组元素个数大于CPU个数
      else if (n >= NCPU || cells != as)
        collide = false;            // At max size or stale
      // 是否有冲突(多个线程访问了cells数组中的同一个Cell)
      else if (!collide)
        collide = true;
      // 只有当前元素个数没有达到CPU个数,并且有冲突,会进行扩容
      // 进行扩容时casCellsBusy方法来将cellsBusy设置为1
      else if (cellsBusy == 0 && casCellsBusy()) {
        try {
          
          if (cells == as) {      // Expand table unless stale
            // 扩容为原来的2倍
            Cell[] rs = new Cell[n << 1];
            // 复制原数组中的Cell元素到新的cells数组中
            for (int i = 0; i < n; ++i)
              rs[i] = as[i];
            cells = rs;
          }
        } finally {
          cellsBusy = 0;
        }
        collide = false;
        continue;                   // Retry with expanded table
      }
      // 重新计算h,找到空闲的Cell
      h = advanceProbe(h);
    }
    // 初始化Cell数组,cellsBusy为0表示当前cells数组没有在被初始化或者扩容,也没有在新建Cell元素;为1表示当前cells数组在被初始化或者扩容,或者在新建Cell元素
    // casCellsBusy来切换cellsBusy的状态
    else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
      boolean init = false;
      try {                           // Initialize table
        if (cells == as) {
          // 初始化的个数为2个
          Cell[] rs = new Cell[2];
          // 使用h&1来计算当前线程应该访问哪个Cell
          rs[h & 1] = new Cell(x);
          cells = rs;
          init = true;
        }
      } finally {
        cellsBusy = 0;
      }
      if (init)
        break;
    }
    else if (casBase(v = base, ((fn == null) ? v + x :
                                fn.applyAsLong(v, x))))
      break;                          // Fall back on using base
  }
}

参考文献

相关推荐
星晨雪海几秒前
优惠券秒杀的核心业务逻辑
java·前端·数据库
阿飞不想努力3 分钟前
文件上传原理与实操
java·spring boot·vue·文件上传
Cx330❀3 分钟前
线程进阶实战:资源划分与线程控制核心指南
java·大数据·linux·运维·服务器·开发语言·搜索引擎
人道领域4 分钟前
【黑马点评日记02】:Session+ThreadLocal实现短信登录
java·开发语言·spring·tomcat·intellij-idea
Bat U10 分钟前
JavaEE|计算机是如何工作的
java·人工智能
许彰午12 分钟前
# 政务表单动态建表?运行时DDL引擎,前端拖完字段后端直接建
java·前端·后端·架构·政务
我登哥MVP14 分钟前
【Spring6笔记】 - 13 - 面向切面编程(AOP)
java·开发语言·spring boot·笔记·spring·aop
宸津-代码粉碎机15 分钟前
Spring Boot 4.0 进阶实战+源码解析系列(持续更新)—— 从落地到源码,搞定面试与工作
java·人工智能·spring boot·后端·python·面试
沐雪轻挽萤16 分钟前
2. C++17新特性-结构化绑定 (Structured Bindings)
java·开发语言·c++
java1234_小锋16 分钟前
Java高频面试题:Kafka的消费消息是如何传递的?
java·开发语言·mybatis