一、簡介
【概述】
JMM(JavaMemoryModel)就是Java記憶體模型,可以把JMM看作是Java定義的併發程式設計相關的一組規範,除了抽象了執行緒和主記憶體之間的關係之外,其還規定了從Java原始碼到CPU可執行指令的這個轉化過程要遵守哪些和併發相關的原則和規範,其主要目的是爲了簡化多執行緒程式設計,增強程式可移植性的。
【作用】
定義了執行緒和主記憶體之間的抽象關係:執行緒之間的共享變數儲存在主記憶體中,每個執行緒都有一個私有的本地記憶體,本地記憶體中儲存了該執行緒以讀/寫共享變數的副本。
【規定】
所有的共享變數都儲存於主記憶體。
變數指的是例項變數和類變數,不包含區域性變數,因為區域性變數是執行緒私有的,因此不存在競爭問題。
每一個執行緒還存在自己的工作記憶體,執行緒的工作記憶體,保留了被執行緒使用的變數的工作副本。
執行緒對變數的所有的操作(讀、寫)都必須在工作記憶體中完成,而不能直接讀寫主記憶體中的變數。
不同執行緒之間也不能直接訪問對方工作記憶體中的變數,執行緒間變數值的傳遞需要透過主記憶體中轉來完成。
【示例圖】
二、八種記憶體操作
鎖定(lock):作用於主記憶體中的變數,將他標記為一個執行緒獨享變數。
解鎖(unlock):作用於主記憶體中的變數,解除變數的鎖定狀態,被解除鎖定狀態的變數才能被其他執行緒鎖定。
read(讀取):作用於主記憶體的變數,它把一個變數的值從主記憶體傳輸到執行緒的工作記憶體中,以便隨後的load動作使用。
load(載入):把read操作從主記憶體中得到的變數值放入工作記憶體的變數的副本中。
use(使用):把工作記憶體中的一個變數的值傳給執行引擎,每當虛擬機器遇到一個使用到變數的指令時都會使用該指令。
assign(賦值):作用於工作記憶體的變數,它把一個從執行引擎接收到的值賦給工作記憶體的變數,每當虛擬機器遇到一個給變數賦值的位元組碼指令時執行這個操作。
store(儲存):作用於工作記憶體的變數,它把工作記憶體中一個變數的值傳送到主記憶體中,以便隨後的write操作使用。
write(寫入):作用於主記憶體的變數,它把store操作從工作記憶體中得到的變數的值放入主記憶體的變數中。
三、三大特徵
JMM三大特徵分別是:原子性、可見性、有序性。整個JMM實際上也是圍繞著這三個特徵建立起來的,並且也是Java併發程式設計的基礎。
1、原子性
【概述】
原子性是指一個操作是不可分割、不可中斷的,要麼全部執行成功要麼全部執行失敗。JMM只能保證對基本資料型別的變數的讀寫操作是原子性的(long和double除外)。
【示例】
int x = 1; // 基本型別賦值操作,必定是原子性操作。 int y = x; // 先讀取x變數的值,再進行賦值給y變數,進行了兩個操作,不能保證原子性。 x++; // 先讀取x變數的值,再進行加1,最後再賦值給x變數,進行了三個操作,不能保證原子性。
【實現方式】
Java提供了synchronized關鍵字。在synchronized修飾的程式碼塊之間的操作都是原子性的。
2、可見性
【概述】
可見性是指所有執行緒都能看到共享記憶體的最新狀態。即當一個執行緒修改了一個共享變數的值時,其他執行緒能夠立即看到該變數的最新值。
【實現方式】
volatile關鍵字:當一個共享變數被volatile關鍵字修飾時,這個變數被修改後會立即重新整理到主記憶體,保證其他執行緒看到的值一定是最新的。
final關鍵字:final修飾的變數,在構造器中一旦初始化完成,如果沒有物件逸出(指物件沒有初始化完成就可以被別的執行緒使用),那麼其他執行緒都就可以看見該變數。
synchronized關鍵字:執行緒進入synchronized程式碼塊後,執行緒會獲取到lock,將會清空本地記憶體,然後從主記憶體中複製共享變數的最新值到本地記憶體作為副本,執行程式碼,又將修改後的副本值重新整理到主記憶體中,最後執行緒執行unlock。
3、有序性
【概述】
有序性是指程式執行的順序按照程式碼的先後順序執行。
【實現方式】
volatile關鍵字:透過在主存中加入記憶體屏障來達到禁止指令重排序,來保證有序性。
synchronized關鍵字:一個變數在同一時刻只能被一個執行緒lock,並且必須unlock後,其他執行緒纔可以重新lock,使得被synchronized修飾的程式碼塊在多執行緒之間是序列執行的。