Java中的 AtomicReference類概覽及實現(xiàn)方案
一、引言
在 Java 開發(fā)中,常常需要構建無鎖的應用程序,以實現(xiàn)高性能的并發(fā)控制。AtomicReference 就是它們中極其重要的一員。作為一種基于 CAS 機制實現(xiàn)的原實性工具,它在構建無鎖隊列、單例、上下文切換等場景中有著重要地位。
本文將隨著一個體系化的進程,從基本概念到源碼分析,展示 AtomicReference 在實際中如何做到 "精精不苦無鎖同步"。
二、AtomicReference 概覽
2.1 概念介紹
AtomicReference 是一個對應任意對象的原實性工具類,繼承自 java.util.concurrent.atomic.AtomicReference<T>,提供類似于基本型的 atomicXXX 類,但對象化。
它內(nèi)部基于 Unsafe 的 CAS 操作,能夠精確地實現(xiàn)與目標對象的引用替換。
2.2 類結構快覽
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 的基礎用法
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é)將覆蓋類結構、關鍵方法、內(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;
// 關鍵字段,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)存結構中的偏移量。- 所有 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ū)懭?,適用于延遲可見的情況,性能更優(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 架構下的 CMPXCHG)。其關鍵邏輯:
- 比較對象
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ā)。
小結
AtomicReference實現(xiàn)基于Unsafe的 CAS 操作,繞過 synchronized 實現(xiàn)高性能原子更新。valueOffset是通過反射獲取的內(nèi)存偏移,用于精確定位對象字段。compareAndSet調(diào)用的是 JVM 層原子指令(cmpxchg),確保并發(fā)安全。volatile和lazySet提供不同程度的內(nèi)存可見性策略,需根據(jù)具體業(yè)務場景選擇。
六、常見問題及解決方案
AtomicReference 雖然為無鎖編程提供了便捷手段,但在實踐中仍然存在若干值得注意的問題。特別是在高并發(fā)環(huán)境下,如果理解不當,可能會引入隱蔽的并發(fā) bug。本章節(jié)總結了實際使用中遇到的典型問題及對應解決策略。
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)化手段
小結
| 問題 | 現(xiàn)象 | 解決方式 |
|---|---|---|
| ABA 問題 | 值恢復原樣但中途被修改 | 使用 AtomicStampedReference |
| 引用更新原子但狀態(tài)不安全 | 內(nèi)部字段可能并發(fā)讀寫沖突 | 使用不可變對象整體替換 |
| CAS 自旋性能差 | 多線程競爭失敗導致 CPU 消耗上升 | 增加退避策略 / 限制自旋次數(shù) |
在實際應用中,合理選擇原子類并避免過度依賴單一機制是保障系統(tǒng)健壯性的關鍵。
到此這篇關于Java 中的 AtomicReference 類及其實現(xiàn)的文章就介紹到這了,更多相關Java AtomicReference 類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java?Springboot實現(xiàn)教務管理系統(tǒng)
這篇文章主要介紹了java?Springboot實現(xiàn)教務管理系統(tǒng)的過程,文章圍繞實現(xiàn)過程展開全文詳細內(nèi)容,具有一定的參考價值,需要的朋友可以參考一下,希望對你有所幫助2021-11-11
SpringBoot中的JPA(Java?Persistence?API)詳解
這篇文章主要介紹了SpringBoot中的JPA(Java?Persistence?API)詳解,JPA用于將?Java?對象映射到關系型數(shù)據(jù)庫中,它提供了一種面向?qū)ο蟮姆绞絹聿僮鲾?shù)據(jù)庫,使得開發(fā)者可以更加方便地進行數(shù)據(jù)持久化操作,需要的朋友可以參考下2023-07-07
Java事務@Transactional?、@GlobalTransactional使用步驟
這篇文章主要介紹了使用@Transactional和@GlobalTransactional注解進行事務管理的區(qū)別和應用場景,@Transactional用于單個服務內(nèi)的本地事務管理,而@GlobalTransactional用于跨服務的分布式事務管理,文中給出了詳細的代碼示例,需要的朋友可以參考下2025-05-05
基于Javamail實現(xiàn)發(fā)送郵件(QQ/網(wǎng)易郵件服務器)
這篇文章主要介紹了基于Javamail實現(xiàn)發(fā)送郵件,分別使用QQ郵箱作為smtp郵件服務器發(fā)送郵件,使用網(wǎng)易郵箱作為smtp郵件服務器發(fā)送郵件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08

