1、內存模型的概述
Java內存模型(即Java Memory Model,簡稱JMM)本身是一種抽象的概念,並不真實存在,它描述的是一組規則或規範,通過這組規範定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。
由於JVM運行程序的實體是線程,而每個線程創建時JVM都會爲其創建一個工作內存(有些地方稱爲棧空間),用於存儲線程私有的數據,而Java內存模型中規定所有變量都存儲在主內存,主內存是共享內存區域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內存中進行。
首先要將變量從主內存拷貝的自己的工作內存空間,然後對變量進行操作,操作完成後再將變量寫回主內存,不能直接操作主內存中的變量,工作內存中存儲着主內存中的變量副本拷貝,前面說過,工作內存是每個線程的私有數據區域,因此不同的線程間無法訪問對方的工作內存,線程間的通信(傳值)必須通過主內存來完成。
2、內存模型的示意圖
主內存:
主要存儲的是Java實例對象,所有線程創建的實例對象都存放在主內存中,不管該實例對象是成員變量還是方法中的本地變量(也稱局部變量),當然也包括了共享的類信息、常量、靜態變量。由於是共享數據區域,多條線程對同一個變量進行訪問可能會發現線程安全問題。
工作內存:
主要存儲當前方法的所有本地變量信息(工作內存中存儲着主內存中的變量副本拷貝),每個線程只能訪問自己的工作內存,即線程中的本地變量對其它線程是不可見的,就算是兩個線程執行的是同一段代碼,它們也會各自在自己的工作內存中創建屬於當前線程的本地變量,當然也包括了字節碼行號指示器、相關Native方法的信息。注意由於工作內存是每個線程的私有數據,線程間無法相互訪問工作內存,因此存儲在工作內存的數據不存在線程安全問題。
3、JMM數據原子操作
4、內存模型工作流程圖
上圖jmm工作流程:
步驟一
線程1:先把initFlag變量從主內存中read讀取出來,再load載入自己的工作內存,use使用線程1執行代碼!initFlag。
步驟二
線程2:先把initFlag變量從主內存中read讀取出來,再load載入自己的工作內存,use使用線程2執行代碼initFlag=true,這時候值發生了改變,線程會再assign重新賦值到工作內存,然後store存儲並寫入主內存,write寫入賦值到主內存中的變量。
(線程2對緩存行lock加鎖,write寫入主內存後會解鎖unlock,防止initFlag還未write寫入主內存就被線程1讀取爲false)。
步驟三
線程1:因爲initFlag被volatile修飾,使用MESI緩存一致性協議,線程1利用cpu總線嗅探機制監聽到了initFlag值的修改,線程1中工作內存initFlag=false失效,這時候線程就拿不到工作內存的數據了,從而工作內存繼續去讀取主內存數據,變爲true退出循環繼續執行,體現了多線程同步運行共享變量副本的可見性。如果initFlag沒有被volatile修飾,線程1將感知不到initFlag的變化,一直循環下去停止不了。
實例代碼:
public class VolatileVisibilityTest {
//volatile變量,用來確保將變量的更新操作通知到其他線程。
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("waiting data...");
while (!initFlag) {
}
System.out.println("==============success");
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
prepareData();
}
}).start();
}
public static void prepareData(){
System.out.println("preparing data...");
initFlag = true;
System.out.println("prepare data end...");
}
}
以上加了“volatile”關鍵字,實現了共享變量的可見性問題
volatile是怎麼實現的呢?原理是怎樣的?
volatile底層原理不是Java來編寫的,而是用彙編語言的來編寫
很早以前是用總線的加鎖的機制來做到可見性和一致性的
lock和unlock會對主內存加鎖的,總線加鎖一般不使用,效率太低,跟單線程差不多。一般用MESI緩存一致性協議。