Java8 原子弹类之LongAdder源码分析

简介: 简单来说,这个类用于在多线程情况下的求和。官方文档的说明从关键方法add包含了一个Cell数组,Striped64的一个内部类Padded variant of AtomicLong supporting only raw accesses plus CAS即AtomicLong的填充变体且只支持原始访问和CAS有一个value变量,并且提供了一个cas方法更新value值接下来看第一个if语句,这句首先判断cells是否还没被初始化,并且尝试对value值进行cas操作。

简单来说,这个类用于在多线程情况下的求和。


img_9e1cee6d831024aa26fbfb41ccf8a5a6.png
官方文档的说明

从关键方法

add

img_c1e167ed96bdbcbeb456d3504501b761.png

包含了一个Cell数组, Striped64的一个内部类
img_dc1c48d17ff9fff0ed7827bc5d305572.png

Padded variant of AtomicLong supporting only raw accesses plus CAS
AtomicLong的填充变体且只支持原始访问和CAS
有一个value变量,并且提供了一个cas方法更新value值

接下来看第一个if语句,这句首先判断cells是否还没被初始化,并且尝试对value值进行cas操作。如果cells已经初始化并且cas操作失败,则运行if内部的语句。在进入第一个if语句之后紧接着是另外一个if,这个if有4个判断:cell[]数组是否初始化;cell[]数组虽然初始化了但是数组长度是否为0;该线程所对应的cell是否为null;尝试对该线程对应的cell单元进行cas更新是否失败,如果这些条件有一条为true,则运行最为核心的方法longAccumulate,下面列出这个方法,为了便于理解,直接将对其的分析写为注释。


img_69d87ad65dce0624c86cb7de4ab093fa.png
JavaDoc
/**
  * 处理涉及初始化,调整大小,创建新Cell,和/或争用的更新案例
  *
  * @param x 值
  * @param fn 更新方法
  * @param wasUncontended 调用
  */
 final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
     int h;
     // 获取线程probe的值
     if ((h = getProbe()) == 0) {
         // 值为0则初始化
         ThreadLocalRandom.current(); //强制初始化
         h = getProbe();
         wasUncontended = true;
     }
     boolean collide = false;                // True if last slot nonempt
     for (;;) {
         Cell[] as; Cell a; int n; long v;
         // 这个if分支处理上述四个条件中的前两个相似,此时cells数组已经初始化了并且长度大于0
         if ((as = cells) != null && (n = as.length) > 0) {
             // 线程对应的cell为null
             if ((a = as[(n - 1) & h]) == null) {
                 // 如果busy锁未被占有
                 if (cellsBusy == 0) {       // Try to attach new Cell
                     // 新建一个cell
                     Cell r = new Cell(x);   // Optimistically create
                     // 检测busy是否为0,并且尝试锁busy
                     if (cellsBusy == 0 && casCellsBusy()) {
                         boolean created = false;
                         try {               // Recheck under lock
                             Cell[] rs; int m, j;
                             //再次确认线程probe所对应的cell为null,将新建的cell赋值
                             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;
             }
             //置为true后交给循环重试
             else if (!wasUncontended)       // CAS already known to fail
                 wasUncontended = true;      // Continue after rehash
             //尝试给线程对应的cell update
             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;
             //在以上条件都无法解决的情况下尝试扩展cell
             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还未进行第一次初始化,进行初始化
         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;
         }
         //busy锁不成功或者忙,则再重试一次casBase对value直接累加
         else if (casBase(v = base, ((fn == null) ? v + x :
                                     fn.applyAsLong(v, x))))
             break;                          // Fall back on using base
     }
 }
  /**
   * Spinlock (locked via CAS) used when resizing and/or creating Cells.
   * 通过cas实现的自旋锁,用于扩大或者初始化cells
   */
  transient volatile int cellsBusy;

从以上分析来看,longAccumulate就是为了尽量减少多个线程更新同一个value,实在不行则扩大cell

LongAdder减少冲突的方法以及在求和场景下比AtomicLong更高效。
因为LongAdder在更新数值时并非对一个数进行更新,而是分散到多个cell,这样在多线程的情况下可以有效的嫌少冲突和压力,使得更加高效。

使用场景

适用于统计求和计数的场景,因为它提供了addsum方法

LongAdder是否能够替换AtomicLong

从上面的分析来看是不行的,因为AtomicLong提供了很多cas方法,例如getAndIncrementgetAndDecrement等,使用起来非常的灵活,而LongAdder只有addsum,使用起来比较受限。
优点:由于 JVM 会将 64位的double,long 型变量的读操作分为两次32位的读操作,所以低并发保持了 AtomicLong性能,高并发下热点数据被 hash 到多个 Cell,有限分离,通过分散提升了并行度
但统计时有数据更新,也可能会出现数据误差,但高并发场景有限使用此类,低时还是可以继续 AtomicLong

目录
相关文章
|
25天前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
77 4
|
30天前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
128 5
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
109 1
|
1月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
168 1
|
1月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
118 0
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
117 1
|
1月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
206 5
|
2月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
116 0
|
2月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
183 16