目錄
1 AtomicLong
1.1 核心功能
1.2 實現原理:
(1)基于 Unsafe 的底層操作
(2) volatile字段的內存可見性
(3)CAS 操作與 ABA 問題
1.3 性能分析
1.4 使用場景
2 LongAdder
核心設計原理
1 分段存儲
2 分散更新策略
3.處理高競爭
1 AtomicLong
AtomicLong
是一個基于 CAS操作的原子類,用于在多線程環境下對 long
類型變量進行無鎖的原子性操作。它通過底層的 Unsafe
類實現高效的原子更新,解決傳統 synchronized
關鍵字帶來的性能開銷問題。
1.1 核心功能
AtomicLong
支持以下原子操作:
方法 | 功能 | 實現原理 |
| 原子性地將變量增加 | CAS 操作 |
| 原子性自增 1 并返回新值 |
|
| 原子性自減 1 并返回新值 |
|
| 原子性增加 并返回原值 | CAS 操作 |
| 原子性設置新值并返回原值 | CAS 操作 |
| 原子性將值從 更新為 (如果相等) | CAS 操作 |
| 獲取當前值(非原子,但保證可見性) | 直接讀取 |
1.2 實現原理:
(1)基于 Unsafe
的底層操作
AtomicLong
的所有原子方法均通過 sun.misc.Unsafe
類的底層原生指令實現,例如:
objectFieldOffset:獲取long value字段在對象內存中的偏移量
getAndAddLong:通過CPU的CAS指令(如 cmpxchgq
)原子性更新內存中的值。
(2) volatile字段的內存可見性
AtomicLong
的 value
字段聲明為 volatile
,確保一個線程的修改對其它線程可見:
(3)CAS 操作與 ABA 問題
- CAS 操作
CAS(Compare-And-Swap)包含三個步驟:
- 比較:讀取內存中的舊值
expect
。- 交換:如果舊值等于
expect
,則將新值update
寫入內存;否則不操作。- 返回結果:返回內存中的舊值。
- ABA 問題
- 場景:變量從 A → B → A,此時
compareAndSet(A, C)
會錯誤地認為值未變,導致更新失敗。- 解決方案:
- AtomicStampedReference:引入版本戳(stamp)標記變量的修改次數,解決 ABA 問題。
- LongAdder:通過分散更新壓力避免單一變量的頻繁 CAS 沖突。
1.3 性能分析
優勢
- 無鎖操作:避免線程阻塞和上下文切換,性能優于
synchronized
。- 讀寫高效:
get()
方法是非原子的,但保證內存可見性,讀取速度極快。- 適用場景:低到中等并發場景,如計數器、單變量累加器。
局限性
- 高并發瓶頸:當多個線程激烈競爭同一變量時,CAS 失敗次數劇增,導致自旋開銷(spin-wait)。
- ABA 問題:需額外處理或改用
AtomicStampedReference
。
1.4 使用場景
低競爭環境:如單機多線程的計數器、序列號生成。
需要強原子性保證:如銀行賬戶扣款、分布式鎖的版本控制。
2 LongAdder
設計目標:LongAdder
是Java 8引入的原子類,屬于 java.util.concurrent.atomic
包,專為高并發場景下的累加操作優化。它的核心目標是解決AtomicLong 在及極高并發下的性能瓶頸——通過分散壓力來減少線程間的CAS沖突。
核心設計原理
1 分段存儲
- cells數組: LongAdder維護一個動態增長的long[]cells數組,每個元素成為一個cell,用于存儲部分累加值。
- base變量:所有cell之外的累加值存儲在base中,默認情況下,單個線程的增量優先base,當base發生競爭時,才會分配新的cell。
2 分散更新策略
- 優先更新
base
(①):- 若
cells
未初始化(null
),或通過casBase
成功將base
加上x
,直接返回。casBase
是Striped64
提供的原子操作,用于更新base
。
- 若
- 處理
cells
分段(②-④):- ?哈希索引計算:
getProbe()
返回與當前線程綁定的哈希值(通過ThreadLocalRandom
生成),并與數組長度掩碼按位與,得到目標 cell 的索引。 - CAS 更新 cell:若目標 cell 為空,通過
casCell
初始化為新值;否則嘗試原子性增加 cell 的值。 - 競爭標記:若 CAS 失敗(
uncontended = false
),表示存在線程競爭。
- ?哈希索引計算:
3.處理高競爭
調用 longAccumulate
方法,可能觸發以下操作:
- 動態擴容:若
cells
數組過小,倍增其大小并將base
值分布到新 cell 中。 - 批量寫入:將當前線程的增量暫存到臨時變量,直到找到可寫入的 cell。
public void add(long x) {Cell[] as; long b, v; int m; Cell a;if ((as = cells) != null || !casBase(b = base, b + x)) { // ① 優先嘗試更新 baseboolean uncontended = true;if (as == null || (m = as.length - 1) < 0 || // ② 處理未初始化或空數組(a = as[getProbe() & m]) == null || // ③ 計算哈希索引并獲取目標 cell!(uncontended = a.cas(v = a.value, v + x))) { // ④ CAS 更新 celllongAccumulate(x, null, uncontended); // ⑤ 處理競爭,觸發動態擴容}}
}
sum方法:返回base值和cells數組的總和
public long sum() {Cell[] as = cells; Cell a;long sum = base;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)sum += a.value;}}return sum;
}
reset方法:重置變量,使總和保持為零。
此方法可能是創建新 adder 的有用替代方法,但僅在沒有并發更新時有效。由于此方法本質上是 racy,因此僅當已知沒有線程同時更新時,才應使用它。
public void reset() {Cell[] as = cells; Cell a;base = 0L;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null)a.value = 0L;}}
}
sumThenReset方法:返回當前總和后清零,適用于離線統計場景
實際上等效于 sum 后跟 reset。例如,在多線程計算之間的 static points 期間,此方法可能適用。如果存在與此方法并發的更新, 則不能保證 返回的值是重置之前出現的最終值。
public long sumThenReset() {Cell[] as = cells; Cell a;long sum = base;base = 0L;if (as != null) {for (int i = 0; i < as.length; ++i) {if ((a = as[i]) != null) {sum += a.value;a.value = 0L;}}}return sum;
}