【併發編程】- 內存模型(針對JSR-133內存模型)篇

併發編程模型

  • 1.兩個關鍵問題

  • 1)線程之間如何通訊

  • 共享內存
    程之間共享程序的公共狀態,經過寫-讀內存中的公共狀態進行隱式通訊程序員

  • 消息傳遞
    程之間沒有公共狀態,線程之間必須經過發送消息來顯式進行通訊編程

  • 2)線程之間如何同步

  • 線程之間沒有公共狀態,線程之間必須經過發送消息來顯式進行通訊數組

總結:Java的併發採用的是共享內存模型,Java線程之間的通訊老是隱式進行,整個通訊過程對程序員徹底透明。緩存

  • 2.抽象結構

  • 1)本地內存

  • 每一個線程都有一個私有的本地內存(LocalMemory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存、寫緩衝區、寄存器以及其餘的硬件和編譯器優化

  • 2)主內存

  • 線程之間的共享變量存儲在主內存

附圖:
image.png多線程

注:全部實例域、靜態域和數組元素都存儲在堆內存中,堆內存在線程之間共享(本章用「共享變量」這個術語代指實例域,靜態域和數組元素)。局部變量(Local Variables),方法定義參數(Java語法規範稱之爲Formal Method Parameters)和異常處理器參數(Exception HandlerParameters)不會在線程之間共享,它們不會有內存可⻅性問題,也不受內存模型的影響。併發


  • 3.重排序


  • 定義:重排序是指編譯器和處理器爲了優化程序性能而對指令序列進行從新排序的一種手段。

  • 1) 3種類型

  • 編譯器優化的重排序(處理器重排序)
  • 編譯器在不改變單線程程序語義的前提下,能夠從新安排語句的執行順序
  • 指令級並行的重排序(處理器重排序)

image.png

現代處理器採用了指令級並行技術(Instruction-Level Parallelism,ILP)來將多條指令重疊執行。若是不存在數據依賴性,處理器能夠改變語句對應機器指令的執行順序
內存系統的重排序
因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行
app

  • 2)數據依賴性


  • 說明:若是兩個操做訪問同一個變量,且這兩個操做中有一個爲寫操做,此時這兩個操做之間就存在數據依賴性。

  • 存在三種類型(只要重排兩個操做執行順序,結果便會被改變)

    • 寫後讀:寫一個變量以後,再讀這個變量
    • 寫後寫:寫一個變量以後,在寫這個變量
    • 讀後寫:讀一個變量以後,再寫這個變量
  • 3)as-if-serial語義

  • 說明:無論怎麼重排序(編譯器和處理器爲了提升並行度),(單線程)程序的執行結果不能被改變。編譯器、runtime和處理器都必須遵照as-if-serial語義。
    • 順序規則:A happens-before B,B happens-before C,happens-before C
      那麼實際執行是B能夠排在A前,JMM容許這種排序。
  • 4)對多線程的影響

  • 對於存證控制依賴的操做重排序,可能會改變程序的執行結果。
  • 5)DCL問題(double check lock)

public class DoubleCheckedLocking { // 1
        private static Instance instance; // 2

        public static Instance getInstance() { // 3
            if (instance == null) { // 4:第一次檢查
                synchronized (DoubleCheckedLocking.class) { // 5:加鎖
                    if (instance == null) // 6:第二次檢查
                        instance = new Instance(); // 7:問題的根源出在這裏
                } // 8
            } // 9
            return instance; // 10
        } // 11
    }
  • 那麼問題就出如今線程執行到第4行,代碼讀取到instance不爲null時,instance引用的對象有可能還
    沒有完成初始化
    • __根源:
memory = allocate();  //1:分配對象的內存空間
    ctorInstance(memory);  //2:初始化對象
    instance = memory;   //3:設置instance指向剛分配的內存地址
  • JMM容許上述命令的執行順序調整爲
memory = allocate();  //1:分配對象的內存空間
    instance = memory;   //3:設置instance指向剛分配的內存地址
                          //注意,此時對象尚未被初始化!
    ctorInstance(memory); //2:初始化對象
  • 問題:爲何要調整這個順序呢?
  • 緣由:這個重排序在沒有改變單線程程序執行結果的前提下,能夠提升程序的執行性能。
    image.png
    image.png
  • 解決方案性能

    • 不容許2和3重排序([volatile]);
    • 容許2和3重排序,但不容許其餘線程「看到」這個重排序。
  • 第一種基於volatile方案優化

public class SafeDoubleCheckedLocking {
        private volatile static Instance instance;

        public static Instance getInstance() {
            if (instance == null) {
                synchronized (SafeDoubleCheckedLocking.class) {
                    if (instance == null)
                        instance = new Instance(); // instance爲volatile,如今沒問題了
                }
            }
            return instance;
        }
    }
  • 第二種基於類初始化方案
public class InstanceFactory {
        private static class InstanceHolder {
            public static Instance instance = new Instance();
        }

        public static Instance getInstance() {
            return InstanceHolder.instance;  // 這裏將致使InstanceHolder類被初始化,存在初始化鎖,拿不到的線程會一直等待
        }
    }

  • 4.happens-before

happens-before是JMM最核心的概念線程

1) 關係的定義

  • 若是⼀個操做happens-before另⼀個操做,那麼第一個操做的執行結果將對第二個操做可見,並且第一個操做的執行順序排在第二個操做以前。[JMM對程序員的承諾]
  • 兩個操做之間存在happens-before關係,並不意味着Java平臺的具體實現必需要按照happens-before關係指定的順序來執行。若是重排序以後的執行結果,與按happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM容許這種重排序)[JMM對編譯器和處理器重排序的約束原則]

2) 規則

  • 程序順序規則:一個線程中的每一個操做,happens-before於該線程中的任意後續操做。
  • 監視器鎖規則:對一個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。
  • volatile變量規則:對一個volatile域的寫,happens-before於任意後續對這個volatile域的讀。
  • 傳遞性:A happens-before B,且B happens-before C,那麼A happens-before C。
  • start()規則:若是線程A執行操做ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操做happens-before於線程B中的任意操做。
  • join()規則:若是線程A執行操做ThreadB.join()併成功返回,那麼線程B中的任意操做happens-before於線程A從ThreadB.join()操做成功返回。

注意:兩個操做之間具備happens-before關係,並不意味着前
一個操做必需要在後一個操做以前執行!happens-before僅僅要求前一個操
做(執行的結果)對後一個操做可見,且前一個操做按順序排在第一個操
做以前(the first is visible to and ordered before the second)。

  • appens-before與JMM的關係
    image.png

話外語:

  • as-if-serial語義保證單線程內程序的執行結果不被改變,happens-before關係保證正確同步的多線程程序的執行結果不被改變
  • as-if-serial語義和happens-before這麼作的目的,都是爲了在不改變程序執行結果的前提下,儘量地提升程序執行的並行度
相關文章
相關標籤/搜索