高并發(fā)計(jì)數(shù)器LongAdder 實(shí)現(xiàn)原理與使用場(chǎng)景詳解
LongAdder 原理與應(yīng)用詳解
一、設(shè)計(jì)背景與核心思想
1. 傳統(tǒng)原子類的性能瓶頸
- AtomicInteger/AtomicLong 基于 CAS 實(shí)現(xiàn)
- 高并發(fā)場(chǎng)景缺陷:
- CAS 失敗率隨競(jìng)爭(zhēng)加劇指數(shù)上升
- CPU 空轉(zhuǎn)消耗大量資源
- 緩存一致性流量(MESI協(xié)議)導(dǎo)致總線風(fēng)暴
2. LongAdder 設(shè)計(jì)目標(biāo)
- 降低競(jìng)爭(zhēng):通過數(shù)據(jù)分片分散寫壓力
- 空間換時(shí)間:犧牲部分內(nèi)存換取更高吞吐
- 最終一致性:允許讀取結(jié)果存在短暫誤差
二、實(shí)現(xiàn)原理剖析
1. 核心數(shù)據(jù)結(jié)構(gòu)
// 基礎(chǔ)值(無競(jìng)爭(zhēng)時(shí)直接操作)
transient volatile long base;
// 分片單元數(shù)組(應(yīng)對(duì)高并發(fā))
transient volatile Cell[] cells;
// 分片單元結(jié)構(gòu)(避免偽共享)
@jdk.internal.vm.annotation.Contended
static final class Cell {
volatile long value;
Cell(long x) { value = x; }
}2. 分段累加流程


3. 偽共享解決方案
- 問題根源:不同線程的變量共享同一緩存行(通常 64 字節(jié))
- 優(yōu)化方案:
- 使用
@Contended注解自動(dòng)填充 - 每個(gè) Cell 獨(dú)占緩存行
- 內(nèi)存布局示意:
- 使用
| Cell1 (64字節(jié)) | Cell2 (64字節(jié)) | ... |
三、關(guān)鍵操作解析
1. 累加操作(add)
public void add(long x) {
Cell[] cs; long b, v; int m; Cell c;
if ((cs = cells) != null ||
!casBase(b = base, b + x)) {
boolean uncontended = true;
if (cs == null || (m = cs.length - 1) < 0 ||
(c = cs[getProbe() & m]) == null ||
!(uncontended = c.cas(v = c.value, v + x)))
longAccumulate(x, null, uncontended);
}
}執(zhí)行策略:
- 優(yōu)先嘗試更新 base
- 失敗后定位到線程對(duì)應(yīng)的 Cell
- 多級(jí)失敗后觸發(fā)數(shù)組擴(kuò)容
2. 取值操作(sum)
public long sum() {
Cell[] cs = cells;
long sum = base;
if (cs != null) {
for (Cell c : cs)
if (c != null) sum += c.value;
}
return sum;
}特點(diǎn):
- 非原子快照(可能包含進(jìn)行中的更新)
- 時(shí)間復(fù)雜度 O(n)(需遍歷所有 Cell)
四、示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.LongAdder;
public class SimpleLongAdderExample {
public static void main(String[] args) throws InterruptedException {
// 1. 創(chuàng)建LongAdder實(shí)例
LongAdder counter = new LongAdder();
// 2. 創(chuàng)建線程池(模擬并發(fā)請(qǐng)求)
ExecutorService executor = Executors.newFixedThreadPool(10);
// 3. 提交100個(gè)累加任務(wù)
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 每個(gè)任務(wù)累加1000次
for (int j = 0; j < 1000; j++) {
counter.increment(); // 等同于add(1)
}
});
}
// 4. 關(guān)閉線程池并等待任務(wù)完成
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// 5. 輸出最終結(jié)果
System.out.println("最終計(jì)數(shù): " + counter.sum()); // 應(yīng)輸出100000
}
}五、性能對(duì)比數(shù)據(jù)
測(cè)試環(huán)境:
- CPU:8 核 Intel i9-9900K
- 內(nèi)存:32GB DDR4
- JVM:OpenJDK 17
- 測(cè)試用例:32 線程執(zhí)行 1 億次累加
| 實(shí)現(xiàn)方案 | 耗時(shí) (ms) | 吞吐量 (ops/ms) | 內(nèi)存占用 |
|---|---|---|---|
| synchronized | 4,520 | 22,123 | 低 |
| AtomicLong | 1,280 | 78,125 | 低 |
| LongAdder | 235 | 425,531 | 中 |
| ThreadLocal 優(yōu)化 | 182 | 549,450 | 高 |
六、應(yīng)用場(chǎng)景指南
1. 推薦使用場(chǎng)景
| 場(chǎng)景類型 | 典型用例 | 優(yōu)勢(shì)說明 |
|---|---|---|
| 高頻計(jì)數(shù)器 | 網(wǎng)站 PV/UV 統(tǒng)計(jì) | 分散寫競(jìng)爭(zhēng) |
| 監(jiān)控指標(biāo)采集 | QPS/TPS 統(tǒng)計(jì) | 允許最終一致性 |
| 分布式限流 | 令牌桶算法實(shí)現(xiàn) | 避免 CAS 失敗風(fēng)暴 |
| 大數(shù)據(jù)聚合 | 實(shí)時(shí)計(jì)算中間結(jié)果 | 支持快速并行累加 |
2. 不適用場(chǎng)景
| 場(chǎng)景類型 | 典型用例 | 問題分析 |
|---|---|---|
| 精確原子操作 | 庫存扣減 | sum() 非原子快照 |
| 讀多寫少 | 配置項(xiàng)更新 | AtomicLong 更高效 |
| 內(nèi)存敏感場(chǎng)景 | 海量獨(dú)立計(jì)數(shù)器 | Cell 數(shù)組內(nèi)存開銷大 |
七、實(shí)現(xiàn)原理總結(jié)
| 設(shè)計(jì)要點(diǎn) | 實(shí)現(xiàn)方案 | 解決的問題 |
|---|---|---|
| 競(jìng)爭(zhēng)分散 | 分片 Cell 數(shù)組 | 降低 CAS 失敗率 |
| 偽共享預(yù)防 | @Contended 注解 | 提升緩存利用率 |
| 動(dòng)態(tài)擴(kuò)容 | 按需創(chuàng)建 Cell | 平衡性能與內(nèi)存 |
| 延遲初始化 | 初始使用 base 變量 | 減少內(nèi)存開銷 |
| 最終一致性 | sum() 合并所有 Cell | 保證最終結(jié)果正確性 |
通過理解 LongAdder 的設(shè)計(jì)哲學(xué)和實(shí)現(xiàn)細(xì)節(jié),開發(fā)者可以在高并發(fā)場(chǎng)景中做出更優(yōu)的技術(shù)選型,在保證線程安全的前提下實(shí)現(xiàn) 5-10 倍的性能提升。關(guān)鍵是要根據(jù)實(shí)際業(yè)務(wù)場(chǎng)景的讀寫比例、一致性要求和資源限制進(jìn)行合理選擇。
到此這篇關(guān)于高并發(fā)計(jì)數(shù)器LongAdder 實(shí)現(xiàn)原理與使用場(chǎng)景詳解的文章就介紹到這了,更多相關(guān)LongAdder原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Controller層和Service層的類報(bào)錯(cuò)問題及解決方案
這篇文章主要介紹了關(guān)于Controller層和Service層的類報(bào)錯(cuò)問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Springboot項(xiàng)目啟動(dòng)時(shí)端口被占用的問題及解決
這篇文章主要介紹了Springboot項(xiàng)目啟動(dòng)時(shí)端口被占用的問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-05-05
SpringBoot項(xiàng)目啟動(dòng)慢的5個(gè)優(yōu)化技巧
SpringBoot以其約定優(yōu)于配置的理念和快速開發(fā)的能力,已成為Java生態(tài)中最受歡迎的框架之一,然而,隨著項(xiàng)目規(guī)模的擴(kuò)大,許多開發(fā)者會(huì)發(fā)現(xiàn)一個(gè)令人頭疼的問題:應(yīng)用啟動(dòng)時(shí)間越來越長(zhǎng),本文將深入分析SpringBoot啟動(dòng)慢的根本原因,并分享5個(gè)經(jīng)過實(shí)戰(zhàn)驗(yàn)證的優(yōu)化技巧2026-03-03
Java實(shí)現(xiàn)的對(duì)稱加密算法AES定義與用法詳解
這篇文章主要介紹了Java實(shí)現(xiàn)的對(duì)稱加密算法AES,結(jié)合實(shí)例形式分析了對(duì)稱加密算法AES的定義、特點(diǎn)、用法及使用場(chǎng)景,需要的朋友可以參考下2018-04-04
java字節(jié)碼框架ASM的深入學(xué)習(xí)
這篇文章主要給大家介紹了java中字節(jié)碼框架ASM的相關(guān)資料,文中介紹的非常詳細(xì),相信對(duì)大家的理解和學(xué)習(xí)具有一定的參考借鑒價(jià)值,有需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-01-01
Java使用OTP動(dòng)態(tài)口令(每分鐘變一次)進(jìn)行登錄認(rèn)證
這篇文章主要介紹了Java使用OTP動(dòng)態(tài)口令(每分鐘變一次)進(jìn)行登錄認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
java對(duì)xml節(jié)點(diǎn)屬性的增刪改查實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄猨ava對(duì)xml節(jié)點(diǎn)屬性的增刪改查實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10

