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

