概述
LongAdder是JDK1.8之后新引入的原子类,可应用于高并发写共享变量场景(以下翻译摘自源码):
当多个线程更新一个用于统计等目的(而非用于细粒度同步控制)的公共总和时,此类通常比AtomicLong更可取。在低更新争用情况下,这两个类的特性相似。但在高争用情况下,此类的预期吞吐量显著提高,但代价是更高的空间消耗。 LongAdder可以与ConcurrentHashMap一起使用,以维护一个可扩展的频率映射(一种直方图或多重集合的形式)。例如,要向`ConcurrentHashMap<String,LongAdder> freqs`中添加一个计数(如果尚不存在则进行初始化),可以使用`freqs.computeIfAbsent(k -> new LongAdder()).increment();`。
理解LongAdder之前,可以通过与AtomicLong进行对比,以便更好地理解LongAdder。
AtomicLong工作原理
AtomicLong是Java并发包(java.util.concurrent.atomic)中的一个类,提供了一种线程安全的方式来进行长整型数据的原子操作。其底层工作原理主要基于CAS(Compare-And-Swap)操作,以下是对其底层工作原理的详细解释:
一、CAS操作
CAS(Compare-And-Swap)操作是一种基于硬件的原子操作,可以在多线程环境下实现对共享变量的原子访问。它包含了三个操作数:
- 内存地址V:表示要更新的变量在内存中的地址。
- 预期值A:表示如果内存地址V中的当前值等于A,才进行更新操作。
- 新值B:表示如果内存地址V中的当前值等于A,则将V的值更新为B。
CAS操作的执行流程如下:
- 比较内存地址V中的当前值是否等于预期值A。
- 如果相等,则将内存地址V的值更新为新值B。
- 如果不相等,则不做任何操作。
CAS操作是无锁的,因此可以避免传统锁机制带来的性能开销。但是,由于CAS操作是基于硬件实现的,其性能可能会受到硬件平台和操作系统的影响。
二、AtomicLong的CAS实现
AtomicLong内部使用一个volatile修饰的long型变量来存储数值,这个变量用于保存被操作的长整型值。AtomicLong通过sun.misc.Unsafe类来实现底层的CAS操作。Unsafe类提供了一些底层操作,如直接访问内存、修改对象字段等。这个类在JDK中并不是公共API,因此使用时需要谨慎。
AtomicLong的CAS实现流程如下:
- 当线程尝试更新AtomicLong的值时,会先获取当前内存地址上的值。
- 将这个值与预期值进行比较。
- 如果相等,则使用CAS操作将新值写入内存地址。
- 如果不相等,则说明有其他线程已经更新了该值,此时当前线程会重新获取内存地址上的新值,并再次进行比较和更新操作,直到成功为止。
这种基于CAS的更新方式可以确保对变量的读写操作具有原子性,并且能够利用底层硬件的原子指令或者类似机制来达到高效的并发处理。
三、AtomicLong的性能问题
在高并发场景下,AtomicLong的性能可能会受到一定影响,因为多个线程同时尝试执行CAS操作时,只有一个线程能够成功。其他线程在CAS操作失败后,会通过循环重试的方式再次尝试,直到成功为止。这种循环重试机制可能会导致CPU资源的浪费和性能下降。
因此为了解决AtomicLong的CAS自旋造成的CPU空转消耗问题,Doug Lea大神在JDK1.8开始为我们引出了LongAddr原子类。
LongAdder工作原理
旨在解决高并发环境下Atomic类频繁CAS(Compare-And-Swap)操作导致的CPU资源浪费问题。其工作原理主要基于分段锁机制,通过牺牲一定的空间来换取更高的时间效率,即在多线程环境中提高性能。以下是LongAdder工作原理的详细解释:
一、核心思想
LongAdder的核心思想是分散热点,将原本由单个变量(如AtomicLong中的value)承担的更新压力分散到多个变量(即LongAdder中的Cell数组)中去。这样,不同线程会命中数组的不同槽位,各自只对自己槽位的值进行CAS操作,从而大大降低了并发冲突的概率。
二、主要成员变量
LongAdder继承了Striped64类,并主要利用了以下几个关键成员变量:
- base:一个
volatile修饰的long类型变量,用于在无竞争或低竞争情况下存储总和值。 - cells:一个
volatile修饰的Cell[]数组,用于在高竞争情况下存储分段的总和值。Cell是Striped64的一个静态内部类,其中包含一个volatile long value变量来存储分段值。 - cellsBusy:一个
volatile int变量,用于指示cells数组是否正在被初始化或扩容。它有两个值:0表示无锁状态,1表示其他线程已经持有了锁。
三、工作原理
-
无竞争情况:
- 在最初无竞争时,LongAdder只更新base的值。此时,add方法会尝试通过CAS操作将值累加到base上。
-
出现竞争:
- 当多个线程同时尝试更新base值时,CAS操作可能会失败。此时,LongAdder会创建cells数组,并将更新压力分散到数组的各个Cell中。
- 每个线程会根据其线程ID的哈希值映射到cells数组的某个下标,然后对该下标所对应的Cell值进行CAS操作。
- 如果某个Cell尚未初始化,则当前线程会尝试创建该Cell,并将其值设置为需要增加的值。这个过程需要获取cellsBusy锁来确保线程安全。
-
扩容机制:
- 当cells数组中的竞争变得非常激烈时(例如,数组长度达到了CPU核心数或扩容阈值),LongAdder会尝试扩容cells数组。
- 扩容过程也需要获取cellsBusy锁,并创建一个新的、长度是原数组两倍的新数组。然后,将旧数组中的元素复制到新数组中,并更新cells引用为新数组。
-
获取总和:
- LongAdder的sum方法会遍历cells数组,并将所有Cell的值和base值相加,得到最终的总和值。
LongAdder使用示例
package com.test;
import java.util.concurrent.atomic.LongAdder;
/**
* @ClassName : LongAdderTest
* @Description:
* @Author: liulianglin
* @Date: 2024-10-26 10:10
* @Version : 1.0
*/
public class LongAdderTest {
public static void main(String[] args) {
LongAdder longAdder = new LongAdder();
for (int i =0; i< 100; i++){
new Thread(()->{
// 下面的代码可以在多线程中安全进行,不用考虑加锁
longAdder.increment();
}, " thread-"+i).start();
}
while(Thread.activeCount() > 2){
// 当活跃线程数大于设定的默认线程数的时候 主线程让步
Thread.yield();
}
// 当前累加器的值
long sum = longAdder.sum();
System.out.println(sum);
}
}
执行结果:

710

被折叠的 条评论
为什么被折叠?



