Java併發編程之Java內存模型詳解

個人博客請訪問 http://www.x0100.top      

1. JMM抽象結構模型

JMM抽象結構模型

JMM定義了線程和主內存之間的抽象關係:

  1. 線程之間的共享變量存儲在主內存中

  2. 每個線程都有一個私有的本地內存,本地內存中存儲了該線程用以讀/寫共享變量的副本

共享變量:堆內存在線程之間共享,存儲在堆內存中所有實例域、靜態域和數組元素都是共享變量

Java內存模型

線程之間通信

線程A與線程B通信:

  1. 線程A把本地內存A中的共享變量刷新到主內存中去。

  2. 線程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解決可見性和有序性問題

  1. 要求程序員都去搞懂重排序以及JMM內存屏障再去編程是不現實的。

  2. JMM提供了簡單易懂的happens-before原則,並向程序員保證執行併發程序會遵守happens-before原則。

  3. 程序員只需理解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關係。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章