深入理解Java內存模型JMM與volatile關鍵字
多核並發緩存架構
Java內存模型
Java線程內存模型跟CPU緩存模型類似,是基於CPU緩存模型來建立的,Java線程內存模型是標准化的,屏蔽掉了底層不同計算機的區別。
例子
編寫代碼來分析
public class VolatileVisibilityTest {
private static boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("等待數據准備..");
while (!initFlag){
}
System.out.println("============數據准備完畢,執行程序邏輯");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData(){

System.out.println("數據准備中..");
initFlag = true;
System.out.println("數據准備完畢!");
}
}
執行程序,打印結果
並未出現
============數據准備完畢,執行程序邏輯
這段結果
分析
第一個線程給了initFlag為false,第二個執行了prepareData()所以initFlag為true,但是第一個線程中的flag還是為false。
如果給initFlag加個volatile
關鍵字:
public class VolatileVisibilityTest {
private static volatile boolean initFlag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("等待數據准備..");
while (!initFlag){
}
System.out.println("============數據准備完畢,執行程序邏輯");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData(){
System.out.println("數據准備中..");
initFlag = true;
System.out.println("數據准備完畢!");
}
}
執行程序,返回結果
JMM數據原子操作
- read(讀取):從主內存讀取數據
- load(載入):將主內存讀取到的數據寫入工作內存
- use(使用):從工作內存讀取數據來計算
- assign(賦值):將計算好的值重新賦值到工作內存中
- strore(存儲):將工作內存數據寫入主內存
- write(寫入):將store過去的變量值賦值給主內存中的變量
- lock(鎖定):將主內存變量枷鎖,表示為線程獨占狀態
- unlock(解鎖):將主內存變量解鎖,解鎖后其他線程可以鎖定該變量
整個過程如下
JMM緩存不一致問題
-
總線枷鎖(性能太低)
- CPU從主內存讀取數據到高速緩存,會在總線對這個數據加鎖,這樣其他CPU沒法去讀或寫這個數據,直到這個CPU使用完整數據釋放鎖之后其他CPU才能讀取該數據。
-
MESI緩存一致性協議
- 多個CPU從主內存讀取同一個數據到各自的高速緩存,當其中某個CPU修改了緩存里的數據,該數據會馬上同步回主內存,其他CPU通過
總線嗅探機制
可以感知到數據的變化從而將自己緩存里的數據失效。
- 多個CPU從主內存讀取同一個數據到各自的高速緩存,當其中某個CPU修改了緩存里的數據,該數據會馬上同步回主內存,其他CPU通過
Volatile可見性底層實現原理
-
Volatile緩存可見性實現原理
- 底層實現主要是通過匯編lock前綴指令,它會鎖定這塊內存區域的緩存並回寫到主內存,此操作被稱為“緩存鎖定”,MESI緩存一致性協議機制會阻止同時修改被兩個以上處理器緩存的內存區域數據。一個處理器的緩存值通過總線回寫到內存會導致其他處理器響應的緩存失效。
Java程序匯編代碼查看
- -server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*VolatileVisibilityTest.prepareData
- 需要先下載hsdis-amd64
IDEA這樣設置
顯式出的結果,其中volatile修飾的匯編代碼如下:
0x000000000349eaff:
lock
add dword ptr [rsp],0h ;*putstatic initFlag
; - com.tugohost.concurrent.VolatileVisibilityTest::prepareData@9 (line 31)
可見性、原子性與有序性
- 並發編程三大特性:可見性,原子性,有序性
- Volatile保證可見性與有序性,但是不保證原子性,保證原子性需要借助synchronized這樣的鎖機制。