個人博客請訪問 http://www.x0100.top
1. JMM抽象結構模型
JMM抽象結構模型
JMM定義了線程和主內存之間的抽象關係:
-
線程之間的共享變量存儲在主內存中
-
每個線程都有一個私有的本地內存,本地內存中存儲了該線程用以讀/寫共享變量的副本
共享變量:堆內存在線程之間共享,存儲在堆內存中所有實例域、靜態域和數組元素都是共享變量
Java內存模型
線程之間通信
線程A與線程B通信:
-
線程A把本地內存A中的共享變量刷新到主內存中去。
-
線程B到主內存中去讀取線程A之前已更新過的共享變量。
從整體來看,這個過程就是線程A在向線程B發送消息。這個通信過程必須要經過主內存。JMM通過控制主內存與每個線程的本地內存之間的交互,來爲Java程序員提供內存可見性保證。
舉例:
public class JMMTest {
static int a = 0;// 主內存中的共享變量
public static void main(String[] args) {
new Thread() {
public void run() {
a = 1;// 線程本地內存中操作共享變量a,並將a=1刷新到豬內存中
while(true) {// 測試用,爲了保持線程運行
}
};
}.start();
new Thread() {
public void run() {
System.out.println(a);// 線程到主內存中讀取變量a
while(true) {
}
};
}.start();
}
}
兩個線程之間的通信過程如下圖:
2. JMM解決可見性和有序性問題
-
要求程序員都去搞懂重排序以及JMM內存屏障再去編程是不現實的。
-
JMM提供了簡單易懂的happens-before原則,並向程序員保證執行併發程序會遵守happens-before原則。
-
程序員只需理解happens-before原則,按照happens-before原則寫併發代碼,就能保證內存可見性和有序性。
JMM的設計
1.程序員對內存模型的使用
程序員希望內存模型易於理解、易於編程。程序員希望基於一個強內存模型來編寫代碼。
JMM向程序員提供的happens-before規則,簡單易懂且提供了足夠強的內存可見性保證。程序員可以把happens-before規則當做強內存模型看待。
2.編譯器和處理器對內存模型的實現
編譯器和處理器希望內存模型對它們的束縛越少越好,這樣它們就可以做盡可能多的優化來提高性能。編譯器和處理器希望實現一個弱內存模型。
JMM遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。
例如這些優化既不會改變程序的執行結果,又能提高程序的執行效率。
1.如果編譯器經過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖可以被消除。
2.如果編譯器經過細緻的分析後,認定一個volatile變量只會被單個線程訪問,那麼編譯器可以把這個volatile變量當作一個普通變量來對待。
如圖,程序員、happens-before、JMM之間的關係:
3. happens-before
一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。
兩個操作可以是單線程或多線程,happens-before解決的就是多線程內存可見性問題。區分數據依賴性和as-if-seial針對單線程。
happens-before原則定義如下:
1)一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
2)兩個操作之間存在happens-before關係,並不意味着一定要按照happens-before原則制定的順序來執行。如果重排序之後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。
happens-before原則規則:
1)程序次序規則:一個線程內,按照代碼順序,書寫在前面的操作先行發生於書寫在後面的操作;
2)鎖定規則:一個unLock操作先行發生於後面對同一個鎖額lock操作;
3)volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作;
4)傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C;
5)線程啓動規則:Thread對象的start()方法先行發生於此線程的每個一個動作;
6)線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生;
7)線程終結規則:線程中所有的操作都先行發生於線程的終止檢測,我們可以通過 Thread.join()方法結束、Thread.isAlive()的返回值手段檢測到線程已經終止執行;
8)對象終結規則:一個對象的初始化完成先行發生於他的finalize()方法的開始;
JMM與原子性問題
Java內存模型只保證了基本讀取和賦值是原子性操作,如果要實現更大範圍操作的原子性,需要通過互斥加鎖synchronized和Lock來實現。
總結
JMM定義了線程和主內存之間的抽象關係,共享變量存儲在主內存中,線程本地內存中存儲了該線程用以讀/寫共享變量的副本。
JMM向程序員提供的happens-before規則來解決可見性和有序性問題。
一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。