JMM記憶體模型詳解(一)
本文開始死磕JMM(Java記憶體模型)由於知識點較多,分來寫
該文為JMM第一篇
技術往往是枯燥的,本文文字較多
1. JMM是什麼?
其實JMM很好理解,我簡單的解釋一下,在Java多執行緒中我們經常會涉及到兩個概念就是執行緒之間是如何通訊和執行緒之間的同步,那什麼是執行緒之間的通訊呢,其實就是兩個執行緒之間互相交換資訊執行緒之間通訊的方式共有兩種:一種就是共享記憶體,和訊息傳遞
。在共享記憶體中的併發模型中執行緒是通過讀取主記憶體的共享資訊來進行隱性通訊的。在訊息傳遞通訊中執行緒之間沒有公共的狀態,只能通過傳送訊息來進行顯性通訊。然而這只是執行緒通訊,那麼同步呢,同步就是在多執行緒的情況下有順序的去執行。在共享記憶體中同步時顯式進行的,在程式碼中我們必須要去指定方法需要同步執行比如說加同步鎖等。在訊息傳遞的併發模型中傳送訊息必須是在消接收之前,所以同步時隱式的。
2.為什麼要涉及到執行緒併發通訊
java記憶體模型其實可以說是Java併發記憶體模型,在Java中是採用的共享記憶體模型的方式,所以Java執行緒之間的通訊是隱式進行的,對我們是完全透明的,如果你不瞭解通訊機制的話會產生各種執行緒可見性的問題。其實在Java中所有的靜態域,域和陣列元素都存在堆記憶體中,堆記憶體線上程中是共享的一般我們都稱之為共享變數,區域性變數,方法定義引數和異常處理引數不會線上程中共享,所以不會存線上程可見性的問題。上面我就說過執行緒之間的通訊是由JMM來進行控制的,JMM來決定了一個執行緒操作了共享變數後如何對另一個執行緒可見。從上面所說的概念來看的話,JMM定義了執行緒與主記憶體的關係。
3.JMM規定
其實這樣做的原因就是Java是跨平臺語言,在個作業系統中記憶體都有一定的差異性,這樣久造成了併發不一致,所以JMM的作用就是用來遮蔽掉不同作業系統中的記憶體差異性來保持併發的一致性。同時JMM也規範了JVM如何與計算機記憶體進行互動。簡單的來說JMM就是Java自己的一套協議來遮蔽掉各種硬體和作業系統的記憶體訪問差異,實現平臺一致性達到最終的"一次編寫,到處執行",說了這麼多,JMM到底是怎麼控制的呢?然後如何通訊的呢?我們繼續往下看。
4.模型
JMM是一個抽象的概念,並不是真實的存在,它涵蓋了緩衝區,暫存器以及其他硬體和編譯器優化。
Java記憶體模型抽象圖如下:
從上圖可以看出每個執行緒都有一個本地記憶體,如果執行緒想要通訊的話要執行一下步驟:
- A執行緒先把本地記憶體的值寫入主記憶體
- B執行緒從主記憶體中去讀取出A執行緒寫的值
再看下面的這個圖,表示了A如何向B傳送訊息
假設這時候有一個共享變數X預設值都是為0,那麼執行緒A把X的值修改為1,這時候如何才能同步到B執行緒呢。
如果A執行緒把X修改成1之後,A執行緒會把X從A的本地記憶體中寫入到主記憶體中,這樣的話主記憶體的X就等於1了,這時候B執行緒就會去讀取主記憶體的X變數,存入B的本地記憶體中,這樣B執行緒的X變數值也就會變成了1。這樣對嗎。那現在如何通訊我是知道了關鍵它究竟是如何來實現的,就是如何來實現通訊的呢?
5.通訊
上面所說的步驟其實就是實現了執行緒之間的通訊,但是不要以為執行緒之間的通訊就是這麼簡單的,其實在Java中JMM記憶體模型定義了八種操作來實現同步的細節。
- read 讀取,作用於主記憶體把變數從主記憶體中讀取到本本地記憶體。
- load 載入,主要作用本地記憶體,把從主記憶體中讀取的變數載入到本地記憶體的變數副本中
- use 使用,主要作用本地記憶體,把工作記憶體中的一個變數值傳遞給執行引擎,每當虛擬機器遇到一個需要使用變數的值的位元組碼指令時將會執行這個操作。、
- assign 賦值 作用於工作記憶體的變數,它把一個從執行引擎接收到的值賦值給工作記憶體的變數,每當虛擬機器遇到一個給變數賦值的位元組碼指令時執行這個操作。
- store 儲存 作用於工作記憶體的變數,把工作記憶體中的一個變數的值傳送到主記憶體中,以便隨後的write的操作。
- write 寫入 作用於主記憶體的變數,它把store操作從工作記憶體中一個變數的值傳送到主記憶體的變數中。
- lock 鎖定 :作用於主記憶體的變數,把一個變數標識為一條執行緒獨佔狀態。
- unlock 解鎖:作用於主記憶體變數,把一個處於鎖定狀態的變數釋放出來,釋放後的變數才可以被其他執行緒鎖定。
所以看似簡單的通訊其實是這八種狀態來實現的。
同時在Java記憶體模型中明確規定了要執行這些操作需要滿足以下規則:
- 不允許read和load、store和write的操作單獨出現。
- 不允許一個執行緒丟棄它的最近assign的操作,即變數在工作記憶體中改變了之後必須同步到主記憶體中。
- 不允許一個執行緒無原因地(沒有發生過任何assign操作)把資料從工作記憶體同步回主記憶體中。
- 一個新的變數只能在主記憶體中誕生,不允許在工作記憶體中直接使用一個未被初始化(load或assign)的變數。即就是對一個變數實施use和store操作之前,必須先執行過了assign和load操作。
- 一個變數在同一時刻只允許一條執行緒對其進行lock操作,lock和unlock必須成對出現
- 如果對一個變數執行lock操作,將會清空工作記憶體中此變數的值,在執行引擎使用這個變數前需要重新執行load或assign操作初始化變數的值
- 如果一個變數事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他執行緒鎖定的變數。
- 對一個變數執行unlock操作之前,必須先把此變數同步到主記憶體中(執行store和write操作)。
所以上面說的操作要嚴格執行。
目前寫了這麼多,下文預告:
LinkedHashMap原始碼分析
參考資料《深入Java記憶體模型》