Java的內存模型
Java內存模型(JMM)是一個抽象的模型。決定了線程主要定義了線程和內存間的抽象關系:主內存存放的是線程共享變量,每個線程有自己的工作內存,存放變量的副本,只能對副本進行讀寫,副本的變量再刷新到主內存中。具體體現為多核CPU,每核有一個高速緩存,每個核的線程對高速緩存讀寫,並且有共同的主存。
主內存與工作線程交互的操作有以下八種:
lock(鎖定):作用於主內存的變量,它把一個變量標識為一條線程獨占的狀態
unlock(解鎖):作用於主內存的變量,釋放鎖定狀態的變量
read(讀取):作用於主內存的變量,把一個變量從主內存傳輸到線程的工作內存中,以便隨后的load動作使用
load(載入):作用於工作內存的變量,把read操作從主內存中得到的變量值放入工作內存的變量副本中。
use(使用):作用於工作內存的變量,把工作內存中一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用到變量的值的字節碼指令時將會執行這個操作
assign(賦值):作用於工作內存的變量,把一個從執行引擎收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時將會執行這個操作
store(存儲):作用於工作內存的變量,把工作內存的一個變量值傳送到主內存,以便隨后的write操作使用
write(寫入):作用於主內存的變量,把store操作從工作內存得到的變量的值放入主內存變量中
上述八種操作均是原子操作。

如上圖,若A和B兩個線程同時去主存讀寫變量C,就會存在線程安全問題(可見性和有序性)。
Happens-Before原則
在JMM中,兩個線程操作之間存在happens-before關系,則前一個操作的結果對后續操作可見。即使編譯器進行指令重排序的優化,如果結果和重排序前一致,也是允許的。
因為允許指令重排序,這也說明 happens-before並不代表操作的時間順序。
有如下8條規則:
1.程序次序規則:單線程內,按照程序順序,書寫在前面的操作 happens-before 於書寫在后面的操作;
2.volatile變量規則:對一個變量的寫操作 happens-before 於后面對這個變量的讀操作(保證了volatile變量的可見型);
3.傳遞規則:如果 A happens-before B,而B happens-before C,則A happens-before C;
4.鎖定規則:一個unLock操作happens-before 后面對同一個鎖的lock操作;
5.線程啟動規則:A線程調用B線程的B.start()方法和調用之前的操作 happens-before 於B線程中的任意操作;
6.線程終結規則:線程中所有的操作都 happens-before 於線程的終止檢測,如B線程的操作都happens-before 於B.join();
7.線程中斷規則:對線程interrupt()方法的調用 happens-before 於被中斷線程的代碼檢測到中斷事件的發生;
8.對象終結規則:一個對象的初始化完成 happens-before 他的finalize()方法的開始
Volatile關鍵詞
java1.5之后,通過happen-before原則增強了volatile關鍵詞。volatile關鍵詞是輕量的實現線程安全的方法,保證了volatile變量的有序性和可見性。
1.volatile保證了變量在線程間的可見性( MESI 協議):
當寫 volatile 變量時,JMM 會立即該線程對應的本地內存中的共享變量值刷新到主內存。
當讀 volatile 變量時,JMM 會把該線程對應的本地內存置為無效。線程接下來將從主內存中讀取共享變量。
volatile 保證內存可見性,其實是用到了 CPU 保證緩存一致性的 MESI 協議。當某線程對 volatile 變量的修改會立即回寫到主存中,並且導致其他線程的緩存行失效,強制其他線程再使用變量時,需要從主存中讀取。
2.volatile保證了有序性(內存屏障):
volatile變量在讀寫操作前后添加內存屏障,禁止了指令的重排序,保證了有序性。
內存屏障有LoadLoad屏障,StoreStore屏障,LoadStore屏障,StoreLoad屏障。比如loadload屏障,加在Load1; Load2兩個原子操作之間 ,保證在Load2及后續的讀操作讀取之前,Load1已經讀取。其他同理。
在每個volatile寫入之前,插入一個StoreStore,寫入之后,插入一個StoreLoad
在每個volatile讀取之前,插入LoadLoad,之后插入LoadStore。
禁止重排序規則如下圖。

3.volatile無法保證原子性,只能保證自身讀寫為原子操作。
對volatile變量的讀寫 相當與給讀寫方法加了synchronized關鍵詞,但volatile更輕量級。
例子
最經典的例子就是單例模式的雙重檢查鎖模式

這里instance變量需要設置為volatile類型,這樣可以保證它的new操作不會被重排序。
因為這段代碼可能在重排序后分為下列三步執行:
- 為 instance分配內存空間
- 將 instance指向分配的內存地址
- 初始化 instance
這樣在執行2時,別的線程就會判斷instance不為null,直接返回還未實例化完全的instance。
並且可以知道volatile修飾的instance變量的new操作並不是原子操作,否則也就不需要synchronized加鎖來保證原子性了。
總結:
JMM內存模型定義了線程和內存間的抽象關系,實際的例子就是cpu核線程,高速緩存和主存間的關系;
happens-before原則的規定保證了操作間的可見性。
volatile變量保證了有序性和可見性。主要是內存屏障和MESI 協議實現的。volatile變量的讀寫操作為原子操作,本身其他操作(如自增)是非原子操作。
