LongAdder ,这哥们劲儿大(一)

简介: 我们在之前的文章中介绍到了 AtomicLong ,如果你还不了解,我建议你阅读一下这篇文章

我们在之前的文章中介绍到了 AtomicLong ,如果你还不了解,我建议你阅读一下这篇文章

一场 Atomic XXX 的魔幻之旅

为什么我要先说 AtomicLong 呢?因为 LongAdder 的设计是根据 AtomicLong 的缺陷来设计的。

为什么引入 LongAdder

我们知道,AtomicLong 是利用底层的 CAS 操作来提供并发性的,比如 addAndGet 方法

public final long addAndGet(long delta) {
  return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}

我们还知道,CAS 是一种轻量级的自旋方法,它的逻辑是采用自旋的方式不断更新目标值,直到更新成功。(也即乐观锁的实现模式)

在并发数量比较低的场景中,线程冲突的概率比较小,自旋的次数不会很多。但是,在并发数量激增的情况下,会出现大量失败并不断自旋的场景,此时 AtomicLong 的自旋次数很容易会成为瓶颈。

为了解决这种缺陷,引入了本篇文章的主角 --- LongAdder,它主要解决高并发环境下 AtomictLong 的自旋瓶颈问题。

认识 LongAdder

我们先看一下 JDK 源码中关于 LongAdder 的讨论

微信图片_20220417153215.png

这段话很清晰的表明,在多线程环境下,如果业务场景更侧重于统计数据或者收集信息的话,LongAdder 要比 AtomicLong 有更好的性能,吞吐量更高,但是会占用更多的内存。在并发数量较低或者单线程场景中,AtomicLong 和 LongAdder 具有相同的特征,也就是说,在上述场景中,AtomicLong 和 LongAdder 可以互换。

首先我们先来看一下 LongAdder类的定义:

微信图片_20220417153219.jpg

可以看到,LongAdder 继承于 Striped64类,并实现了 Serializable 接口。

Striped64 又是继承于 Number 类,现在你可能不太清楚 Striped64 是做什么的,但是你别急。

微信图片_20220417153222.jpg

我们知道,Number 类是基本数据类型的包装类以及原子性包装类的父类,继承 Number 类表示它可以当作数值进行处理,然而这样并不能说明什么,它对我们来说还是一头雾水,随后我翻了翻 Striped64 的继承体系,发现这个类有几个实现,而这几个实现能够很好的帮助我们理解什么是 Striped64。

微信图片_20220417153229.png

在其中我们看到了 Accumulator,它的中文概念就是累加器,所以我们猜测 Striped64 也能实现累加的功能。果不其然,在我求助了部分帖子之后,我们可以得出来一个结论:Striped64 就是一个支持 64 位的累加器

但是那两个 Accumulator 的实现可以实现累加,这个好理解,但是这两个 Adder 呢?它们也能实现累加的功能吗?

我们再次细致的查看一下 LongAdder 和 DoubleAdder 的源码(因为这两个类非常相似)。

(下面的叙事风格主要以 LongAdder 为准,DoubleAdder 会兼带着聊一下,但不是主要叙事对象,毕竟这两个类的基类非常相似。)

image.gif

在翻阅一波源码过后,我们发现了一些方法,比如 increment、decrement、add、reset、sum等,这些方法看起来好像是一个累加器所拥有的功能。

在详细翻阅源码后,我认证了我的想法,因为 increment 、decrement 都是调用的 add 方法,而 add 方法底层则使用了 longAccmulate,我给你画个图你就知道了。

微信图片_20220417153232.jpg

所以,LongAdder 的第一个特性就是可以处理数值,而实现了 Serializable 这个接口表示 LongAdder 可以序列化,以便存储在文件中或在网络上传输。

然后我们继续往下走,现在时间的罗盘指向了 LongAdder 的 add 方法,我们脑子里面已经有印象,这个 LongAdder 方法是来做并发累加的,所以这个 add 方法如果不出意外就是并发的累加方法,那么很奇怪了,这个方法为什么没有使用 synchronized 呢?

我们继续观察 add 方法都做了哪些操作。

微信图片_20220417153237.jpg

开始时就是分配了一些变量而已啊,这也没什么特殊的,继续看到第二行的时候,我们发现有一个 cells 对象,这个 cells 对象是个数组,好像是个全局的。。。。。。那么在哪里定义的呢?

一想到还有个 Striped64 这个类,果断点进去看到了这个全局变量,另外,Striped64 这个类很重要,我们可不能把它忘了。

微信图片_20220417153240.jpg

在 Striped64 类中,赫然出现了下面这三个重要的全局变量。

微信图片_20220417153242.jpg

由 transient 修饰的这三个全局变量,会保证不会被序列化。

我们先不解释这三个变量是什么,有什么用,因为我们刚刚撸到了 add 方法,所以还是回到 add 方法上来:

继续向下走,我们看到了 caseBase 方法,这个方法又是干什么的?点进去,发现还是 Striped64 中的方法,现在,我们陷入了沉思。

我们上面只是知道了 Striped64 是一个累加器,但是我现在要不要撸一遍 Striped64 的源码??????

认识 Striped64

果然出来混都是要还的,上面没有介绍的 Striped64 的三个变量,现在就要好好介绍一波了。

  • cells 它是一个数组,如果不是 null ,它的大小就是 2 的次方。
  • base 是 Striped64 的基本数值,主要在没有争用时使用,同时也作为 cells 初始化时使用,使用 CAS 进行更新
  • cellsBusy 它是一个 SpinLock(自旋锁),在初始化 cells 时使用。

除了这三个变量之外,还有一个变量,我竟然把它忽视了,罪过罪过。

微信图片_20220417153251.jpg

它表示的是 CPU 的核数。

好像并没有看出来什么玄机,接着往下走

微信图片_20220417153254.jpg

这里出现了一个 Cell 元素的内部类,里面出现了 long 型的 value 值,cas 方法,UNSAFE,valueOffSet 元素,如果你看过我的这篇文章 Atomic 原子工具类详解 或者你了解 AtomicLong 的设计的话,你就知道,Cell 的设计是和 AtomicLong 完全一样的,都是使用了 volatile 变量、Unsafe 加上字段的偏移量,再用 CAS 进行修改。

而这个 Cell 元素,就是 cells 数组中的每个对象。

这里比较特殊的是 @sun.misc.Contended 注解,它是 Java 8 中新增的注解,用来避免缓存的伪共享,减少 CPU 缓存级别的竞争。

害,就这?

先不要着急,Striped64 最核心的功能是分别为 LongAdder 和 DoubleAdder 提供并发累加的功能,所以 Striped64 中的 longAccumulate 和 doubleAccumulate 才是关键,我们主要介绍 longAccumulate 方法,方法比较长,我们慢慢进入节奏。

相关文章
|
3月前
|
监控 安全 IDE
别再瞎用了!synchronized的正确使用姿势在这里!
别再瞎用了!synchronized的正确使用姿势在这里!
61 4
|
5月前
|
安全 Java 程序员
惊呆了!Java多线程里的“synchronized”竟然这么神奇!
【6月更文挑战第20天】Java的`synchronized`关键字是解决线程安全的关键,它确保同一时间只有一个线程访问同步代码。在案例中,`Counter`类的`increment`方法如果不加同步,可能会导致竞态条件。通过使用`synchronized`方法或语句块,可以防止这种情况,确保线程安全。虽然同步会带来性能影响,但它是构建并发应用的重要工具,平衡同步与性能是使用时需考虑的。了解并恰当使用`synchronized`,能有效应对多线程挑战。
20 1
|
6月前
|
存储 缓存 Oracle
Java线程池,白话文vs八股文,原来是这么回事!
一、线程池原理 1、白话文篇 1.1、正式员工(corePoolSize) 正式员工:这些是公司最稳定和最可靠的长期员工,他们一直在工作,不会被解雇或者辞职。他们负责处理公司的核心业务,比如生产、销售、财务等。在Java线程池中,正式员工对应于核心线程(corePoolSize),这些线程会一直存在于线程池中。他们负责执行线程池中的任务,如果没有任务,他们会等待新的任务到来。 1.2、所有员工(maximumPoolSize) 所有员工:这些是公司所有的员工,包括正式员工和外包员工。他们共同组成了公司的团队,协作完成公司的各种业务。在Java线程池中,所有员工对应于所有线程(maxim
|
传感器 缓存 安全
JUC第六讲:二面阿里竟然败在了 volatile 关键字上
JUC第六讲:二面阿里竟然败在了 volatile 关键字上
|
监控 安全 算法
这次锁面试题的连环16问,差点就跪了
这次锁面试题的连环16问,差点就跪了
206 0
面试又被问懵了吗?不如把ThreadLocal拆开了揉碎看看
1.为什么用 ThreadLocal? 所谓并发,就是有限资源需要应对远超资源的访问。解决问题的方法,要么增加资源应对访问;要么增加资源的利用率。 所以,相信这年头做开发的多多少少,都会那么几个“线程二三招”、“用锁五六式”。 那所带来的就是多线程访问下的并发安全问题。 共享变量的访问域跨越了原始的单线程,进入了千家万户的线程眼里。谁都可以用,谁都可以改,那不就打起来了吗? 因此,防止并发问题的最好办法,就是不要多线程访问(这科技水平倒退二十年~)。ThreadLocal 顾名思义,将一个变量限制为“线程封闭”:对象只被一个线程持有、访问、修改。
AtomicXXX 用得好好的,阿里为什么推荐使用 LongAdder?面试必问
面试连环炮 先来一连炮简单的面试,看你能顶住几轮? 栈长: 1、多线程情况下,进行数字累加(count++)要注意什么? 张三: 要注意给累加方法加同步锁,不然会出现变量可见性问题,变量值被其他线程覆盖出现不一致的情况
|
安全 容器
面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?(上)
面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?
203 1
面试阿里被P8质问:ConcurrentHashMap真的线程安全吗?(上)
LongAdder ,这哥们劲儿大(二)
我们在之前的文章中介绍到了 AtomicLong ,如果你还不了解,我建议你阅读一下这篇文章
LongAdder ,这哥们劲儿大(二)
|
缓存 Oracle Java
当Synchronized遇到这玩意儿,有个大坑,要注意! (下)
当Synchronized遇到这玩意儿,有个大坑,要注意! (下)
129 0
当Synchronized遇到这玩意儿,有个大坑,要注意! (下)