了解 volatile 前請先了解 「Java 內(nèi)存模型 (JMM)」是什么
一. volatile 作用是什么
保證變量在多線程之間的可見性
-
防止重排序
- 編譯器重排序
- 指令級重排序
- 內(nèi)存重排序
二. 多線程之間的內(nèi)存可見性是什么,如果不保證可見性會怎么樣
如果不保證可見性的話,比如下面這段代碼, 當變量 asscse 被改變?yōu)?1 后,開啟的 thread 并不會結(jié)束運行,因為 asscse 被拷貝到了線程的工作內(nèi)存中(以 server 模式運行)

三. 通過 JVM 源碼和匯編看怎么保證的多線程間的可見性
1. 準備工作:
運行程序打印匯編代碼的設(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后,運行即可打印匯編代碼

2. 字節(jié)碼
帶 volatile 關(guān)鍵詞時,多了一個 ACC_VOLATILE

3. JVM 源碼
通過 ACC_VOLATILE 看下 JVM 做了啥, 搜索 hotspot 源碼,看到了兩處 is_volatile

繼續(xù)搜索is_volatile 做了什么,我靠這也太多了,160 + 結(jié)果,作為 Java 入道程序員,這個 C++ 看著確實有點拙計,得想點別的辦法。。

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 對應的 fence 和其他函數(shù),感興趣的可以更具體的閱讀:JDK8 hotspot - orderAccess.hpp 源碼

找到 orderAccess.hpp 的實現(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)存的獨占使用。它的作用是能夠?qū)斍疤幚砥鲗彺娴膬?nèi)容刷新到內(nèi)存,并使其他處理器對應的緩存失效。另外還提供了有序的指令無法越過這個內(nèi)存屏障的作用。
正是 lock 實現(xiàn)了 volatile 的「防止指令重排」「內(nèi)存可見」的特性
五. 參考
查看Java的匯編指令Java 內(nèi)存模型
從 HotSpot 源碼看 Java volatile
就是要你懂 Java 中 volatile 關(guān)鍵字實現(xiàn)原理
volatile在java server模式和client模式下的不同(主內(nèi)存和工作內(nèi)存)
Java并發(fā)編程 - volatile