了解 volatile 前請先了解 「Java 內(nèi)存模型 (JMM)」是什么
一. volatile 作用是什么
保證變量在多線程之間的可見性
-
防止重排序
- 編譯器重排序
- 指令級重排序
- 內(nèi)存重排序
二. 多線程之間的內(nèi)存可見性是什么帽撑,如果不保證可見性會怎么樣
如果不保證可見性的話范舀,比如下面這段代碼偷霉, 當(dāng)變量 asscse
被改變?yōu)?1 后颓帝,開啟的 thread 并不會結(jié)束運(yùn)行,因?yàn)?asscse
被拷貝到了線程的工作內(nèi)存中(以 server 模式運(yùn)行)
三. 通過 JVM 源碼和匯編看怎么保證的多線程間的可見性
1. 準(zhǔn)備工作:
運(yùn)行程序打印匯編代碼的設(shè)置
- 下載 https://github.com/evolvedmicrobe/benchmarks/blob/master/hsdis-amd64.dylib
- 將
hsdis-amd64.dylib
放入 %JAVA_HOME%/jre/lib 目錄 - 如下圖补憾,在 intellj idea 中 vm options 中加入
-XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -Xcomp -XX:CompileCommand=dontinline
后酌伊,運(yùn)行即可打印匯編代碼
2. 字節(jié)碼
帶 volatile 關(guān)鍵詞時腾窝,多了一個 ACC_VOLATILE
3. JVM 源碼
通過 ACC_VOLATILE
看下 JVM 做了啥, 搜索 hotspot 源碼居砖,看到了兩處 is_volatile
繼續(xù)搜索is_volatile
做了什么虹脯,我靠這也太多了,160 + 結(jié)果奏候,作為 Java 入道程序員循集,這個 C++ 看著確實(shí)有點(diǎn)拙計,得想點(diǎn)別的辦法蔗草。咒彤。
4. 反編譯為匯編(不帶 volatile 關(guān)鍵詞)
我想看看反匯編出來的代碼在使用和未使用 volatile 關(guān)鍵詞修飾時的差異
5. 反編譯為匯編(帶 volatile 關(guān)鍵詞)
可以看出相對于不帶 volatile 關(guān)鍵字是多了一行 lock addl $0x0,(%rsp)
,開心咒精。镶柱。
6. JVM 源碼
根據(jù) lock addl 搜索到了兩個結(jié)果,哈哈模叙,看到了 '順序存取' 歇拆,打開 orderAccess.hpp 看一下
下面這張圖中看到了,一大篇注釋范咨,在是解釋內(nèi)存模型 (JSR-133)和他們處理內(nèi)存模型的方法其中包含 lock addl 對應(yīng)的 fence 和其他函數(shù)故觅,感興趣的可以更具體的閱讀:JDK8 hotspot - orderAccess.hpp 源碼
找到 orderAccess.hpp 的實(shí)現(xiàn),可以看到 fence 函數(shù)中湖蜕,內(nèi)嵌了匯編逻卖,寫入了 lock addl $0x0,(%rsp)
6. lock addl $0x0,(%rsp) 是什么
IA32
中對 lock 的說明是
The LOCK # signal is asserted during execution of the instruction following the lock prefix. This signal can be used in a multiprocessor system to ensure exclusive use of shared memory while LOCK # is asserted
LOCK 用于在多處理器中執(zhí)行指令時對共享內(nèi)存的獨(dú)占使用宋列。它的作用是能夠?qū)?dāng)前處理器對應(yīng)緩存的內(nèi)容刷新到內(nèi)存昭抒,并使其他處理器對應(yīng)的緩存失效。另外還提供了有序的指令無法越過這個內(nèi)存屏障的作用炼杖。
正是 lock 實(shí)現(xiàn)了 volatile 的「防止指令重排」「內(nèi)存可見」的特性
五. 參考
查看Java的匯編指令Java 內(nèi)存模型
從 HotSpot 源碼看 Java volatile
就是要你懂 Java 中 volatile 關(guān)鍵字實(shí)現(xiàn)原理
volatile在java server模式和client模式下的不同(主內(nèi)存和工作內(nèi)存)
Java并發(fā)編程 - volatile