Java記憶體模型簡介

安全劍客發表於2019-09-11
首先要明確記憶體模型指什麼。書中的定義是:在特定的操作協議下,對特定記憶體和快取記憶體進行讀寫訪問的過程抽象。

可以知道,記憶體模型就是來規定如何對記憶體/快取進行讀寫操作的。所以Java記憶體模型,就是用來定義程式對Java記憶體的的訪問規則。進一步說, Java記憶體模型就是定義程式中變數(靜態變數、陣列物件元素等,不包括區域性變數、方法引數)的訪問規則。

Java記憶體模型的規定:
  1. 所有變數儲存在主記憶體中;
  2. 每個執行緒都有自己的工作記憶體,且對變數的操作都是在工作記憶體中進行;
  3. 不同執行緒之間無法直接訪問彼此工作記憶體中的變數,要想訪問只能透過主記憶體來傳遞。
Java的執行緒、工作記憶體、主記憶體關係如下圖所示:

Java記憶體模型簡介Java記憶體模型簡介
具體變數從主記憶體到工作記憶體,以及從工作記憶體轉回主記憶體的實現細節,由下面八個原子性的操作完成:

    • lock:作用於主記憶體變數,將該變數標識為一個執行緒獨佔的狀態
    • unlock:作用於主記憶體變數,將獨佔狀態釋放
    • read:作用於主記憶體變數,將值複製到工作記憶體中
    • load:作用於工作記憶體中的變數,將值放到工作記憶體中的變數副本中
    • use:作用於工作記憶體中的變數,將值傳給執行引擎
    • asign:作用於工作記憶體中的變數,將執行引擎中的值賦給工作記憶體中的變數
    • store:作用於工作記憶體中的變數,將值傳給主記憶體
    • write:作用於主記憶體中的變數,將工作記憶體中返回的值放到主記憶體變數中

同時還對上述八個操作進行了一些細節的要求,比如read/load、store/write必須成對出現,未執行過lock的變數不能執行unlock操作等。

劃重點,此處面試常遇到的問題就是對於volatile關鍵字的解讀。

volatile關鍵字

此關鍵字修飾的變數具有兩種效果:1、保證執行緒間的可見性;2、阻止指令重排序

對於1的實現,它保證load與use必須相鄰呼叫,即要use這個變數,必定先執行read/load,這樣每次都能獲取到最新的變數值;它又保證asign與store必須相鄰呼叫,即在工作記憶體中將該變數改了之後,必定會先同步到主記憶體中。這樣,volatile關鍵字實現了可見性。至於阻止指令重排序,還是移步《深入理解Java虛擬機器》一書吧,貧道水平有限,就不在這裡說了。

從另一個角度來分析,Java記憶體模型是圍繞著在併發過程中如何處理原子性、可見性、有序性來建立的。
原子性:八個原子性操作,以及synchronized(lock/unlock未直接開放給使用者,synchronized透過monitorenter跟monitorexit指令呼叫的lock/unlock操作)

  • 可見性:volatile、synchronized、final這三個關鍵字均透過不同方式實現了可見性
  • 有序性:volatile、synchronized  這兩個關鍵字保證有序性,同時還有先行發生(happens-before)原則來保證隱含的預設有序性

下面說說happens-before先行發生原則,先行發生原則用通俗語言表述就是:如果操作A在操作B之前發生,那麼A產生的影響B同樣能觀測到。那麼問題來了,先行發生原則都有哪些呢?同樣有八條,如下:

  • 程式次序規則:同一個執行緒中按照程式碼的順序依次執行
  • 管程鎖定規則:對於同一個鎖,unlock先行發生於後面的lock,即unlock了才會lock
  • volatile變數規則:對一個volatile變數的寫操作先行發生於後面對該變數的讀操作,即寫完了才會讀
  • 執行緒啟動規則:一個執行緒的start()方法先行發生於此執行緒的任何一個動作
  • 執行緒終止規則:一個執行緒的所有動作先行發生於該執行緒的終止檢測
  • 執行緒中斷規則:對一個執行緒interrupt()方法的呼叫先行發生於執行緒的中斷檢測Thread.interrpted()
  • 物件終結規則:物件的初始化完成先行發生於finalize()方法
  • 傳遞性:顧名思義,A先行發生於B,B先行發生於C,則A一定先行發生於C

總結

Java記憶體模型基本就這些內容,如果都掌握了的話,非一線網際網路公司基本都能應對自如了(因為一線網際網路公司貧道本人也沒進去><)。

原文連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2656703/,如需轉載,請註明出處,否則將追究法律責任。

相關文章