【原創】Java併發編程系列04 | Java內存模型詳解

【原創】Java併發編程系列04 | Java內存模型詳解

收錄於話題
#進階架構師 | 併發編程專題
12個
點擊上方「java進階架構師」,選擇右上角「置頂公衆號」
20大進階架構專題每日送達
【原創】Java併發編程系列04 | Java內存模型詳解java

思惟導圖

【原創】Java併發編程系列04 | Java內存模型詳解

【原創】Java併發編程系列04 | Java內存模型詳解

寫在前面

前面講解了併發編程的三大核心問題:原子性、可見性、有序性。文章見:【原創】Java併發編程系列03 | 重排序-可見性和有序性問題根源
那麼,做爲從最開始就支持併發的語言,Java是如何解決這些核心問題的呢?程序員

1. JMM抽象結構模型

JMM抽象結構模型面試

JMM定義了線程和主內存之間的抽象關係:
線程之間的共享變量存儲在主內存中
每一個線程都有一個私有的本地內存,本地內存中存儲了該線程用以讀/寫共享變量的副本編程

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

【原創】Java併發編程系列04 | Java內存模型詳解

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();
    }
}

兩個線程之間的通訊過程以下圖:
【原創】Java併發編程系列04 | Java內存模型詳解併發

2. JMM解決可見性和有序性問題

要求程序員都去搞懂重排序以及JMM內存屏障再去編程是不現實的。
JMM提供了簡單易懂的happens-before原則,並向程序員保證執行併發程序會遵照happens-before原則。
程序員只需理解happens-before原則,按照happens-before原則寫併發代碼,就能保證內存可見性和有序性。
JMM的設計app

1.程序員對內存模型的使用
程序員但願內存模型易於理解、易於編程。程序員但願基於一個強內存模型來編寫代碼。
JMM向程序員提供的happens-before規則,簡單易懂且提供了足夠強的內存可見性保證。程序員能夠把happens-before規則當作強內存模型看待。
2.編譯器和處理器對內存模型的實現
編譯器和處理器但願內存模型對它們的束縛越少越好,這樣它們就能夠作儘量多的優化來提升性能。編譯器和處理器但願實現一個弱內存模型。ide

JMM遵循一個基本原則:只要不改變程序的執行結果(指的是單線程程序和正確同步的多線程程序),編譯器和處理器怎麼優化都行。

例如這些優化既不會改變程序的執行結果,又能提升程序的執行效率。

1.若是編譯器通過細緻的分析後,認定一個鎖只會被單個線程訪問,那麼這個鎖能夠被消除。
2.若是編譯器通過細緻的分析後,認定一個volatile變量只會被單個線程訪問,那麼編譯器能夠把這個volatile變量看成一個普通變量來對待。

如圖,程序員、happens-before、JMM之間的關係:

【原創】Java併發編程系列04 | Java內存模型詳解

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關係。
———— e n d ————
微服務、高併發、JVM調優、面試專欄等20大進階架構師專題請關注公衆號【Java進階架構師】後在菜單欄查看。
【原創】Java併發編程系列04 | Java內存模型詳解看到這裏,說明你喜歡本文你的轉發,是對我最大的鼓勵!在看亦是支持↓

相關文章
相關標籤/搜索