Java中的 AtomicReference類概覽及實現(xiàn)方案
一、引言
在 Java 開發(fā)中,常常需要構(gòu)建無鎖的應(yīng)用程序,以實現(xiàn)高性能的并發(fā)控制。AtomicReference 就是它們中極其重要的一員。作為一種基于 CAS 機制實現(xiàn)的原實性工具,它在構(gòu)建無鎖隊列、單例、上下文切換等場景中有著重要地位。
本文將隨著一個體系化的進程,從基本概念到源碼分析,展示 AtomicReference 在實際中如何做到 "精精不苦無鎖同步"。
二、AtomicReference 概覽
2.1 概念介紹
AtomicReference 是一個對應(yīng)任意對象的原實性工具類,繼承自 java.util.concurrent.atomic.AtomicReference<T>,提供類似于基本型的 atomicXXX 類,但對象化。
它內(nèi)部基于 Unsafe 的 CAS 操作,能夠精確地實現(xiàn)與目標對象的引用替換。
2.2 類結(jié)構(gòu)快覽
public class AtomicReference<V> implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile V value;
// ... 重點方法看后續(xù)
}類中主要是一個 volatile 字段 value,和基于 Unsafe 的 CAS 操作支持。
三、核心概念
3.1 CAS 原理
CAS(Compare-And-Swap)是一種無鎖的原實性操作機制,其核心是把當前值和預期值比較,如果相等,則更新為新值。
實現(xiàn)簡化為:
boolean compareAndSet(V expect, V update) {
if (value == expect) {
value = update;
return true;
} else {
return false;
}
}實際中這個操作是依賴硬件持有的原實性 CPU 指令來完成的,在 Java 中通過 Unsafe 來調(diào)用。
3.2 Unsafe 類
Unsafe 是一個本地級類,提供相當不安全的內(nèi)核操作,包括直接操作內(nèi)存,操作類的內(nèi)部字段值等。
四、AtomicReference 的基礎(chǔ)用法
4.1 創(chuàng)建和基本操作
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<String> ref = new AtomicReference<>("initial");
boolean updated = ref.compareAndSet("initial", "updated");
System.out.println("Update successful: " + updated);
System.out.println("Current value: " + ref.get());
}
}輸出:
Update successful: true
Current value: updated
五、源碼深度解析
在深入理解 AtomicReference 的行為機制之前,有必要從源碼級別進行一層層剖析。本章節(jié)將覆蓋類結(jié)構(gòu)、關(guān)鍵方法、內(nèi)存語義以及 Unsafe.compareAndSwapObject 方法的底層實現(xiàn)。
5.1 類定義與字段分析
源碼路徑:java.util.concurrent.atomic.AtomicReference
public class AtomicReference<V> implements java.io.Serializable {
private static final long serialVersionUID = -1848883965231344442L;
// Unsafe 是用于執(zhí)行底層 CAS 操作的核心類
private static final sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
// 用于存儲 value 字段的偏移量,在初始化時通過反射獲取
private static final long valueOffset;
// 關(guān)鍵字段,volatile 保證可見性
private volatile V value;
static {
try {
// valueOffset 的計算,核心反射操作
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
public AtomicReference(V initialValue) {
value = initialValue;
}
public AtomicReference() {
}? 說明:
value是被保護的實際引用,必須是volatile,以確保在并發(fā)線程之間可見。valueOffset是通過反射拿到的value字段在對象內(nèi)存結(jié)構(gòu)中的偏移量。- 所有 CAS 操作都基于
valueOffset。
5.2 核心方法解析
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
public final void set(V newValue) {
value = newValue;
}
public final void lazySet(V newValue) {
unsafe.putOrderedObject(this, valueOffset, newValue);
}
public final V get() {
return value;
}
public final V getAndSet(V newValue) {
while (true) {
V current = get();
if (compareAndSet(current, newValue))
return current;
}
}方法說明:
compareAndSet: 基于 CAS 的原子條件更新,是核心方法。set: 普通賦值操作,具備 volatile 語義。lazySet: 有序?qū)懭耄m用于延遲可見的情況,性能更優(yōu)。getAndSet: 實現(xiàn)方式為循環(huán) CAS。
5.3 內(nèi)存語義分析
volatile語義:保證讀寫操作的可見性,防止指令重排。compareAndSet的語義:具備 volatile 的讀+寫語義,同時含有內(nèi)存屏障(full fence)。lazySet語義:具備“store-store barrier”,只保證新值最終會被線程看到,但不強制立即生效。
內(nèi)存屏障說明:
compareAndSet: LoadLoad + LoadStore + StoreStore + StoreLoad(全屏障) lazySet: StoreStore(寫屏障)
這也是為什么在高并發(fā)但不要求嚴格即時可見性的場景下,lazySet 更高效。
5.4 Unsafe.compareAndSwapObject 深度剖析
該方法定義如下:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
其是 native 方法,最終調(diào)用 JVM 內(nèi)部封裝的 CPU 指令實現(xiàn)(如 x86 架構(gòu)下的 CMPXCHG)。其關(guān)鍵邏輯:
- 比較對象
o中偏移量為offset的字段值是否等于expected - 如果相等,則用
x替換原值,返回 true - 否則不做修改,返回 false
?? 實質(zhì):此操作具備原子性,不會被線程上下文切換打斷。
CPU 層級說明(以 x86 為例)
在底層,JVM 會借助 CPU 提供的原子指令實現(xiàn)上述邏輯,比如:
lock cmpxchg r/m32, r32 // 帶鎖的比較交換,保證總線級別原子
這確保了在多核環(huán)境中即使多個線程同時競爭更新,依然可以避免數(shù)據(jù)競爭。
5.5 JVM 字節(jié)碼觀察
我們通過 javap -c -v 查看 compareAndSet 調(diào)用層次:
javap -c -v java.util.concurrent.atomic.AtomicReference
輸出片段如下:
public final boolean compareAndSet(java.lang.Object, java.lang.Object);
Code:
0: getstatic #16 // Field unsafe:Lsun/misc/Unsafe;
3: aload_0
4: getstatic #18 // Field valueOffset:J
7: aload_1
8: aload_2
9: invokevirtual #24 // Method sun/misc/Unsafe.compareAndSwapObject
12: ireturn
從字節(jié)碼角度也能看出 compareAndSet 調(diào)用是對 Unsafe 對象方法的直接轉(zhuǎn)發(fā)。
小結(jié)
AtomicReference實現(xiàn)基于Unsafe的 CAS 操作,繞過 synchronized 實現(xiàn)高性能原子更新。valueOffset是通過反射獲取的內(nèi)存偏移,用于精確定位對象字段。compareAndSet調(diào)用的是 JVM 層原子指令(cmpxchg),確保并發(fā)安全。volatile和lazySet提供不同程度的內(nèi)存可見性策略,需根據(jù)具體業(yè)務(wù)場景選擇。
六、常見問題及解決方案
AtomicReference 雖然為無鎖編程提供了便捷手段,但在實踐中仍然存在若干值得注意的問題。特別是在高并發(fā)環(huán)境下,如果理解不當,可能會引入隱蔽的并發(fā) bug。本章節(jié)總結(jié)了實際使用中遇到的典型問題及對應(yīng)解決策略。
6.1 問題一:ABA 問題
問題描述:
CAS 操作基于對象的引用地址進行比較,若地址值相同則視為“未變”,這就帶來一個典型并發(fā)陷阱:ABA 問題。
定義:
線程 A 讀取變量為 A,線程 B 將其從 A 改為 B 再改回 A,線程 A 發(fā)現(xiàn)當前還是 A,于是操作成功,但其實已經(jīng)發(fā)生過變化。
示例:
AtomicReference<String> ref = new AtomicReference<>("A");
Thread t1 = new Thread(() -> {
String prev = ref.get(); // 讀取到 A
sleep(100);
boolean success = ref.compareAndSet(prev, "C");
System.out.println("T1 CAS: " + success); // 可能為 true,但實際中間已經(jīng)被改動過
});
Thread t2 = new Thread(() -> {
ref.compareAndSet("A", "B");
ref.compareAndSet("B", "A");
});
t1.start();
t2.start();風險:
雖然最終值為 "A",但實際上經(jīng)歷了變化。CAS 操作未感知這些中間變更,可能導致數(shù)據(jù)一致性問題。
解決方案:AtomicStampedReference
為了解決 ABA 問題,JDK 提供了 AtomicStampedReference 類。它不僅保存值,還維護一個版本號(stamp),每次更新都需同時更新 stamp,從而感知變量是否真正變化過。
示例:
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
Thread t1 = new Thread(() -> {
int[] stamp = new int[1];
String prev = ref.get(stamp);
sleep(100);
boolean success = ref.compareAndSet(prev, "C", stamp[0], stamp[0] + 1);
System.out.println("T1 CAS with stamp: " + success);
});
Thread t2 = new Thread(() -> {
int[] stamp = new int[1];
String curr = ref.get(stamp);
ref.compareAndSet(curr, "B", stamp[0], stamp[0] + 1);
ref.compareAndSet("B", "A", stamp[0] + 1, stamp[0] + 2);
});
t1.start();
t2.start();? 由于 stamp 不同,即使值回到 "A",也能檢測到版本不一致,防止 ABA 問題。
6.2 問題二:引用本身原子,不代表對象狀態(tài)原子
AtomicReference<T> 僅保證對象引用的更新是原子的,并不能保證引用對象內(nèi)部的字段或狀態(tài)是線程安全的。
示例:
class User {
String name;
int age;
}
AtomicReference<User> ref = new AtomicReference<>(new User());
ref.get().age++; // 非原子操作風險:
如果多個線程同時修改 ref.get().age++,依然可能引發(fā)競態(tài)條件。
解決方案:
- 使用
AtomicReference<User>實現(xiàn)整體替換(不可變類方案) - 或封裝更新邏輯,通過 CAS 替換整個對象:
while (true) {
User oldUser = ref.get();
User newUser = new User();
newUser.name = oldUser.name;
newUser.age = oldUser.age + 1;
if (ref.compareAndSet(oldUser, newUser)) break;
}6.3 問題三:頻繁 CAS 導致性能下降
CAS 是樂觀鎖思想,但在高競爭場景下,多次失敗會導致性能問題。
解決策略:
- 使用
LongAdder、StampedLock等替代方案 - 或者使用回退策略、自旋上限等優(yōu)化手段
小結(jié)
| 問題 | 現(xiàn)象 | 解決方式 |
|---|---|---|
| ABA 問題 | 值恢復原樣但中途被修改 | 使用 AtomicStampedReference |
| 引用更新原子但狀態(tài)不安全 | 內(nèi)部字段可能并發(fā)讀寫沖突 | 使用不可變對象整體替換 |
| CAS 自旋性能差 | 多線程競爭失敗導致 CPU 消耗上升 | 增加退避策略 / 限制自旋次數(shù) |
在實際應(yīng)用中,合理選擇原子類并避免過度依賴單一機制是保障系統(tǒng)健壯性的關(guān)鍵。
到此這篇關(guān)于Java 中的 AtomicReference 類及其實現(xiàn)的文章就介紹到這了,更多相關(guān)Java AtomicReference 類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中使用websocket出現(xiàn)404的解決方法
在Springboot中使用websocket時,本地開發(fā)環(huán)境可以正常運行,但部署到服務(wù)器環(huán)境出現(xiàn)404問題,所以本文小編講給大家詳細介紹一下SpringBoot中使用websocket出現(xiàn)404的解決方法,需要的朋友可以參考下2023-09-09
基于Nacos實現(xiàn)Spring Cloud Gateway實現(xiàn)動態(tài)路由的方法
這篇文章主要介紹了基于Nacos實現(xiàn)Spring Cloud Gateway實現(xiàn)動態(tài)路由的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06
Spring boot 跳轉(zhuǎn)到j(luò)sp頁面的實現(xiàn)方法
本篇文章主要介紹了Spring boot 跳轉(zhuǎn)到j(luò)sp頁面的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
java使用itext導出PDF文本絕對定位(實現(xiàn)方法)
下面小編就為大家?guī)硪黄猨ava使用itext導出PDF文本絕對定位(實現(xiàn)方法)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-06-06
Java后臺Controller實現(xiàn)文件下載操作
這篇文章主要介紹了Java后臺Controller實現(xiàn)文件下載操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-10-10
springboot swagger 接口文檔分組展示功能實現(xiàn)
這篇文章主要介紹了springboot swagger 接口文檔分組展示功能實現(xiàn),本文通過實例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧2024-03-03

