Java volatile四種內(nèi)存屏障的作用與生效機制原理詳解
在Java中,volatile關(guān)鍵字是一種輕量級的同步機制,用于確保變量的可見性和有序性。為了實現(xiàn)這些功能,Java虛擬機(JVM)在底層使用了內(nèi)存屏障(Memory Barrier),這些內(nèi)存屏障確保了在多線程環(huán)境下,對共享變量的讀寫操作的正確順序和可見性。
內(nèi)存屏障(Memory Barrier)是處理器提供的一種指令,用于控制指令執(zhí)行順序和內(nèi)存可見性。在Java中,volatile關(guān)鍵字就是通過插入內(nèi)存屏障來實現(xiàn)其內(nèi)存語義的。下面我將詳細解釋四種內(nèi)存屏障的含義和工作原理。
1. 四種基本內(nèi)存屏障
1.1 StoreStore屏障
作用:
- 確保屏障前的所有普通寫操作(store)完成并刷新到主內(nèi)存
- 在屏障后的volatile寫操作之前執(zhí)行
生效機制:
普通寫操作1 普通寫操作2 StoreStore屏障 volatile寫操作
實際效果:保證在volatile變量寫入前,所有之前的普通變量寫入都已經(jīng)完成并可見
1.2 StoreLoad屏障
作用:
- 確保屏障前的所有寫操作(包括volatile寫)完成并刷新到主內(nèi)存
- 在屏障后的所有讀操作(包括volatile讀)之前執(zhí)行
生效機制:
volatile寫操作 StoreLoad屏障 volatile讀操作/普通讀操作
實際效果:這是最"重量級"的屏障,會使該屏障之前的所有內(nèi)存訪問指令(存儲和裝載)完成之后,才執(zhí)行該屏障之后的內(nèi)存訪問指令
1.3 LoadLoad屏障
作用:
- 確保屏障前的所有讀操作(load)完成
- 在屏障后的所有讀操作之前執(zhí)行
生效機制:
volatile讀操作 LoadLoad屏障 普通讀操作/volatile讀操作
實際效果:保證在讀取后續(xù)變量前,先完成對volatile變量的讀取
1.4 LoadStore屏障
作用:
- 確保屏障前的所有讀操作(load)完成
- 在屏障后的所有寫操作之前執(zhí)行
生效機制:
volatile讀操作 LoadStore屏障 普通寫操作/volatile寫操作
實際效果:保證在寫入任何變量前,先完成對volatile變量的讀取
2. 內(nèi)存屏障在volatile中的具體應用
2.1 volatile寫操作的內(nèi)存屏障插入
編譯器會在volatile寫操作前后插入以下屏障:
[普通寫操作] StoreStore屏障 [volatile寫操作] StoreLoad屏障
示例:
x = 42; // 普通寫 y = true; // volatile寫
實際生成的指令序列:
store x, 42 StoreStore屏障 store y, true StoreLoad屏障
2.2 volatile讀操作的內(nèi)存屏障插入
編譯器會在volatile讀操作前后插入以下屏障:
LoadLoad屏障 [volatile讀操作] LoadStore屏障
示例:
if (y) { // volatile讀
z = x; // 普通讀和普通寫
}
實際生成的指令序列:
LoadLoad屏障 load y LoadStore屏障 load x store z, x
3. 內(nèi)存屏障如何保證happens-before關(guān)系
內(nèi)存屏障通過限制處理器和編譯器的重排序來建立happens-before關(guān)系:
- StoreStore屏障:確保volatile寫之前的普通寫操作happens-before volatile寫
- StoreLoad屏障:確保volatile寫happens-before后續(xù)的volatile讀/寫
- LoadLoad屏障:確保volatile讀happens-before后續(xù)的所有讀操作
- LoadStore屏障:確保volatile讀happens-before后續(xù)的所有寫操作
4. 實際處理器中的實現(xiàn)差異
不同處理器架構(gòu)對內(nèi)存屏障的支持不同:
- x86/64:原生支持較強的內(nèi)存模型,只有StoreLoad屏障是真正有作用的
- ARM/PowerPC:需要顯式使用所有四種屏障
- JVM:會根據(jù)目標平臺將Java內(nèi)存屏障映射到具體的處理器指令
例如,在x86上:
- StoreStore屏障通常實現(xiàn)為空操作(no-op)
- StoreLoad屏障實現(xiàn)為
mfence指令或lock前綴指令
5. 示例分析
class ReorderingExample {
int x = 0;
volatile boolean v = false;
void writer() {
x = 42; // 普通寫
v = true; // volatile寫
}
void reader() {
if (v) { // volatile讀
System.out.println(x); // 普通讀
}
}
}內(nèi)存屏障插入后的執(zhí)行順序保證:
- 在writer()中:
x = 42和v = true之間插入StoreStore屏障- 確保x的寫入在v的寫入前完成并可見
- 在reader()中:
if (v)前插入LoadLoad屏障System.out.println(x)前插入LoadStore屏障- 確保讀取v后才讀取x,且讀取的是最新值
6. 為什么需要四種屏障
四種屏障對應不同的讀寫組合,提供了細粒度的控制:
- StoreStore:寫→寫順序
- StoreLoad:寫→讀順序(最常用且開銷最大)
- LoadLoad:讀→讀順序
- LoadStore:讀→寫順序
這種細粒度控制允許JVM在不同架構(gòu)上實現(xiàn)最優(yōu)性能,只在必要的地方插入必要的屏障。
7. 總結(jié)
四種內(nèi)存屏障共同作用,確保了:
- volatile寫的可見性(StoreStore + StoreLoad)
- volatile讀的 freshness(LoadLoad + LoadStore)
- 防止不合理的重排序
- 建立正確的happens-before關(guān)系
以下是四種內(nèi)存屏障的詳細對比表格,展示了它們的特點、作用和區(qū)別:
| 屏障類型 | 插入位置 | 保證的操作順序 | 主要作用 | 典型使用場景 | 開銷級別 |
|---|---|---|---|---|---|
| StoreStore | volatile寫操作之前 | 普通寫 → volatile寫 | 確保volatile寫之前的所有普通寫操作對其它處理器可見 | volatile寫前的普通變量寫入 | 低 |
| StoreLoad | volatile寫操作之后 | volatile寫 → 后續(xù)所有讀 | 確保volatile寫對所有處理器可見后,才能執(zhí)行后續(xù)的讀操作 | volatile寫后可能的讀操作 | 高 |
| LoadLoad | volatile讀操作之前 | volatile讀 → 后續(xù)所有讀 | 確保先完成volatile讀,才能進行后續(xù)的讀操作 | volatile讀后的普通變量讀取 | 中 |
| LoadStore | volatile讀操作之后 | volatile讀 → 后續(xù)所有寫 | 確保先完成volatile讀,才能進行后續(xù)的寫操作 | volatile讀后的普通變量寫入 | 中 |
詳細特性對比
| 特性 | StoreStore | StoreLoad | LoadLoad | LoadStore |
|---|---|---|---|---|
| 防止的重排序類型 | 寫-寫重排序 | 寫-讀重排序 | 讀-讀重排序 | 讀-寫重排序 |
| 保證的可見性 | 使屏障前的寫對所有線程可見 | 使屏障前的寫對所有線程可見 | 確保讀取最新值 | 確?;谧钚轮颠M行寫入 |
| 對應CPU指令 | 通常為no-op(x86) sfence(某些架構(gòu)) | mfence(x86) sync(PowerPC) | lfence(某些架構(gòu)) | 通常組合使用 |
| 發(fā)生頻率 | 每次volatile寫前 | 每次volatile寫后 | 每次volatile讀前 | 每次volatile讀后 |
| 影響范圍 | 僅影響寫操作順序 | 影響寫后所有讀操作 | 僅影響讀操作順序 | 影響讀后所有寫操作 |
| 性能影響 | 較小 | 較大 | 中等 | 中等 |
實際效果示例對比
| 屏障類型 | 代碼示例 (屏障位置) | 保證的效果 |
|---|---|---|
| StoreStore | x=1; [SS]; v=2; | 其他線程看到v=2時,必定能看到x=1 |
| StoreLoad | v=1; [SL]; if(x)... | 執(zhí)行x的讀取時,v=1的寫入已經(jīng)全局可見 |
| LoadLoad | [LL]; if(v)...; tmp=x; | 讀取x時,v的讀取已經(jīng)完成且是最新值 |
| LoadStore | if(v)...; [LS]; x=1; | 寫入x=1時,已經(jīng)基于最新的v值進行了判斷 |
不同處理器架構(gòu)上的表現(xiàn)
| 屏障類型 | x86/64實現(xiàn) | ARM實現(xiàn) | PowerPC實現(xiàn) |
|---|---|---|---|
| StoreStore | 通常不需要(隱式保證) | dmb ishst | lwsync |
| StoreLoad | mfence指令 | dmb ish | sync |
| LoadLoad | 通常不需要(隱式保證) | dmb ishld | lwsync |
| LoadStore | 通常不需要(隱式保證) | dmb ish | lwsync |
這個表格總結(jié)了四種內(nèi)存屏障的關(guān)鍵區(qū)別,理解這些差異對于編寫正確的高性能并發(fā)程序非常重要。實際開發(fā)中,雖然我們很少直接操作這些屏障(它們由JVM自動插入),但了解其原理有助于診斷并發(fā)問題和優(yōu)化性能。
理解這些內(nèi)存屏障的工作原理,有助于深入理解Java內(nèi)存模型和并發(fā)編程中的各種可見性、有序性問題。
到此這篇關(guān)于Java volatile 內(nèi)存屏障詳解:四種內(nèi)存屏障的作用與生效機制的文章就介紹到這了,更多相關(guān)Java volatile 內(nèi)存屏障內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
應用啟動數(shù)據(jù)初始化接口CommandLineRunner和Application詳解
這篇文章主要介紹了應用啟動數(shù)據(jù)初始化接口CommandLineRunner和Application詳解,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java面試題 從源碼角度分析HashSet實現(xiàn)原理
這篇文章主要介紹了Java面試題 從源碼角度分析HashSet實現(xiàn)原理?,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-07-07
SpringBoot整合quartz實現(xiàn)定時任務(wù)
這篇文章主要為大家詳細介紹了SpringBoot如何整合quartz實現(xiàn)定時任務(wù),文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2024-10-10
深入理解Java @Entity注解及其與數(shù)據(jù)庫表的關(guān)聯(lián)
這篇文章主要介紹了深入理解Java @Entity注解及其與數(shù)據(jù)庫表的關(guān)聯(lián),@Entity注解在JPA中占據(jù)核心地位,它建立起Java實體類和數(shù)據(jù)庫表之間的映射關(guān)系,需要的朋友可以參考下2025-05-05
淺談Java編程之if-else的優(yōu)化技巧總結(jié)
說實話,其實我很討厭在代碼里大量使用if-else,一是因為該類代碼執(zhí)行方式屬于面向過程的,二嘛,則是會顯得代碼過于冗余.這篇筆記,主要記錄一些自己在工作實踐當中針對if-else的優(yōu)化心得,將會不定期地長期更新,需要的朋友可以參考下2021-06-06
IDEA在plugins里搜不到mybatisx插件的解決方法
本文主要介紹了IDEA在plugins里搜不到mybatisx插件的解決方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-06-06
JAVA面試題 從源碼角度分析StringBuffer和StringBuilder的區(qū)別
這篇文章主要介紹了JAVA面試題 從源碼角度分析StringBuffer和StringBuilder的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,下面我們來一起學習下吧2019-07-07

