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


3. 偽共享解決方案
- 問題根源:不同線程的變量共享同一緩存行(通常 64 字節(jié))
- 優(yōu)化方案:
- 使用
@Contended注解自動填充 - 每個 Cell 獨占緩存行
- 內存布局示意:
- 使用
| Cell1 (64字節(jié)) | Cell2 (64字節(jié)) | ... |
三、關鍵操作解析
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
- 失敗后定位到線程對應的 Cell
- 多級失敗后觸發(fā)數(shù)組擴容
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;
}特點:
- 非原子快照(可能包含進行中的更新)
- 時間復雜度 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實例
LongAdder counter = new LongAdder();
// 2. 創(chuàng)建線程池(模擬并發(fā)請求)
ExecutorService executor = Executors.newFixedThreadPool(10);
// 3. 提交100個累加任務
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 每個任務累加1000次
for (int j = 0; j < 1000; j++) {
counter.increment(); // 等同于add(1)
}
});
}
// 4. 關閉線程池并等待任務完成
executor.shutdown();
executor.awaitTermination(1, TimeUnit.MINUTES);
// 5. 輸出最終結果
System.out.println("最終計數(shù): " + counter.sum()); // 應輸出100000
}
}五、性能對比數(shù)據
測試環(huán)境:
- CPU:8 核 Intel i9-9900K
- 內存:32GB DDR4
- JVM:OpenJDK 17
- 測試用例:32 線程執(zhí)行 1 億次累加
| 實現(xiàn)方案 | 耗時 (ms) | 吞吐量 (ops/ms) | 內存占用 |
|---|---|---|---|
| synchronized | 4,520 | 22,123 | 低 |
| AtomicLong | 1,280 | 78,125 | 低 |
| LongAdder | 235 | 425,531 | 中 |
| ThreadLocal 優(yōu)化 | 182 | 549,450 | 高 |
六、應用場景指南
1. 推薦使用場景
| 場景類型 | 典型用例 | 優(yōu)勢說明 |
|---|---|---|
| 高頻計數(shù)器 | 網站 PV/UV 統(tǒng)計 | 分散寫競爭 |
| 監(jiān)控指標采集 | QPS/TPS 統(tǒng)計 | 允許最終一致性 |
| 分布式限流 | 令牌桶算法實現(xiàn) | 避免 CAS 失敗風暴 |
| 大數(shù)據聚合 | 實時計算中間結果 | 支持快速并行累加 |
2. 不適用場景
| 場景類型 | 典型用例 | 問題分析 |
|---|---|---|
| 精確原子操作 | 庫存扣減 | sum() 非原子快照 |
| 讀多寫少 | 配置項更新 | AtomicLong 更高效 |
| 內存敏感場景 | 海量獨立計數(shù)器 | Cell 數(shù)組內存開銷大 |
七、實現(xiàn)原理總結
| 設計要點 | 實現(xiàn)方案 | 解決的問題 |
|---|---|---|
| 競爭分散 | 分片 Cell 數(shù)組 | 降低 CAS 失敗率 |
| 偽共享預防 | @Contended 注解 | 提升緩存利用率 |
| 動態(tài)擴容 | 按需創(chuàng)建 Cell | 平衡性能與內存 |
| 延遲初始化 | 初始使用 base 變量 | 減少內存開銷 |
| 最終一致性 | sum() 合并所有 Cell | 保證最終結果正確性 |
通過理解 LongAdder 的設計哲學和實現(xiàn)細節(jié),開發(fā)者可以在高并發(fā)場景中做出更優(yōu)的技術選型,在保證線程安全的前提下實現(xiàn) 5-10 倍的性能提升。關鍵是要根據實際業(yè)務場景的讀寫比例、一致性要求和資源限制進行合理選擇。
到此這篇關于高并發(fā)計數(shù)器LongAdder 實現(xiàn)原理與使用場景詳解的文章就介紹到這了,更多相關LongAdder原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
關于Controller層和Service層的類報錯問題及解決方案
這篇文章主要介紹了關于Controller層和Service層的類報錯問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
Java使用OTP動態(tài)口令(每分鐘變一次)進行登錄認證
這篇文章主要介紹了Java使用OTP動態(tài)口令(每分鐘變一次)進行登錄認證,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-09-09
java對xml節(jié)點屬性的增刪改查實現(xiàn)方法
下面小編就為大家?guī)硪黄猨ava對xml節(jié)點屬性的增刪改查實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10

