volatile關鍵字有兩方面的作用,一是保證共享變量可見性,二是禁止指令重排。
一、內(nèi)存可見性
站在一個java程序員的角度,內(nèi)存可見性應該從兩個方面去理解,多核CPU的緩存一致性,以及JMM多線程線程棧本地內(nèi)存與主存一致性。
1.1、CPU緩存一致性
現(xiàn)代CPU多采用多級緩存架構(gòu),

緩存大大縮小了高速CPU與低速內(nèi)存之間的差距。以三層緩存架構(gòu)為例:
L1 Cache最接近CPU, 容量最小(如32K、64K等)、速度最高,每個核上都有一個L1 Cache。
L2 Cache容量更大(如256K)、速度更低, 一般情況下,每個核上都有一個獨立的L2 Cache。
L3 Cache最接近內(nèi)存,容量最大(如12MB),速度最低,在同一個CPU插槽之間的核共享一個L3 Cache。
在這種架構(gòu)下,可能會存在下面的問題:
1、Core0與Core1命中了內(nèi)存中的同一個地址,那么各自的L1 Cache會緩存同一份數(shù)據(jù)的副本。
2、最開始,Core0與Core1都在友善的讀取這份數(shù)據(jù)。
3、突然,Core0要使壞了,它修改了這份數(shù)據(jù),因為緩存的存在,這個修改并不會馬上同步到主存,二十僅僅修改了Core 0 自己的L1 Cache中的值,此時Core1如果還繼續(xù)以自己L1 Cache中的數(shù)據(jù)為準,必然導致錯誤的結(jié)果。
如何解決這個問題呢?緩存一致性協(xié)議MESI。
關于緩存一致性協(xié)議,可參考下面這篇博客,寫得非常好。
https://www.cnblogs.com/yanlong300/p/8986041.html
簡單來講,就是當某一核改變了共享變量的值,cpu會發(fā)出一個指令,讓其他核心L1 Cache中保存的值變成失效狀態(tài),當其他核心需要讀取這個值時,需要到主存中重新加載。
1.2、工作內(nèi)存與主存的一致性

java每個線程的工作內(nèi)存都會有一個共享變量的備份,若一個線程中的值改變了而未同步到主存,另一個線程可能會讀到臟數(shù)據(jù)。
若共享變量被定義未volatile,則:
寫一個volatile變量時,JMM會把線程對應的工作內(nèi)存中的變量值刷新到主存。
讀一個volatile變量時,JMM會把線程對應的本地內(nèi)存置為無效,然后從主存中讀取該變量。
二、指令重排
指令重排分為編譯器重排與處理器重排,JMM制定了如下的volatile重排序規(guī)則:
| 是否允許重排序 | 第二個操作 | 第二個操作 | 第二個操作 |
|---|---|---|---|
| 第一個操作 | 普通讀/寫 | volatile讀 | volatile寫 |
| 普通讀寫 | 是 | 是 | 否 |
| volatile讀 | 否 | 否 | 否 |
| volatile寫 | 是 | 否 | 否 |
可以看出:
1、當?shù)诙€操作是volatile寫時,不論第一個操作是什么,都不允許重排。
2、當?shù)谝粋€操作是volatile讀時,不論第二個操作是什么,都不允許重排。
3、當?shù)谝粋€操作是volatile寫,第二個操作是volatile讀時,不允許重排。
Java編譯器通過插入內(nèi)存屏障來實現(xiàn)以上規(guī)則。JMM內(nèi)存屏障分為四種:
| 屏障類型 | 指令示例 | 說明 |
|---|---|---|
| LoadLoad Barriers | Load1; LoadLoad; Load2 | 確保load1先于load2 |
| StoreStore Barriers | Store1; StoreStore; Store2 | 確保store1先于store2 |
| LoadStore Barriers | Load1; LoadStore; Store2 | 確保load1先于Store2 |
| StoreLoad Barriers | Store1; StoreLoad; Load2 | 確保store1先于load2 |
采用以下策略進行內(nèi)存屏障插入:
- 在每個volatile寫操作的前面插入一個StoreStore屏障。
- 在每個volatile寫操作的后面插入一個StoreLoad屏障。
- 在每個volatile讀操作的前面插入一個Load Load屏障。
- 在每個volatile讀操作的后面插入一個LoadStore屏障。
參考
http://www.itdecent.cn/p/64240319ed60
https://www.cnblogs.com/yanlong300/p/8986041.html
《Java并發(fā)編程的藝術》