Java內存模型及volatile關鍵字

一、Java內存模型

  1. Java內存模型即爲Java Meomory Model,簡寫爲JMM。它屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺下都能達到一致的內存訪問效果。

  2. 瞭解並掌握工作內存與主內存。
    2.1 創造JMM的主要目的是是定義程序中各個變量的訪問規則,即在JVM中將變量存儲到內存和從內存中取出變量這樣的底層細節。 此處的變量包括實例字段、靜態字段和構成數組對象的元素。即存放在Java堆(Java Heap)方法區(Method Area) 及 **運行時常量池(Runtime Constant Pool)****中的變量,是所有線程共享的。

2.2 二者之間的關係
Java內存模型規定了所有的變量都存儲在主內存中。每條線程還有自己的工作內存,線程的工作內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存進行,而不能直接讀寫主內存中的變量。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成。
需注意: 這裏劃分的工作內存與主內存與Java內存區域中Java堆、棧、方法區等並不是同一層次的劃分。他們之間沒有直接的聯繫。

線程、主內存、工作內存三者的交互關係如下所示 :
在這裏插入圖片描述
2.3 內存間交互操作
關於主內存與工作內存之間的具體交互協議,即一個變量如何從主內存中拷貝到工作內存、如何從工作內存同步回主內存之類的實現細節,Java內存模型中定義瞭如下8種操作來完成。JVM實現時必須保證下面提及的每一種操作的原子的、不可再分的。

(1)lock(鎖定) : 作用於主內存的變量,它把一個變量標識爲一條線程獨佔的狀態
(2)unlock(解鎖) : 作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定。
(3)read(讀取) : 作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用。
(4)load(載入) : 作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
(5)use(使用) : 作用於工作內存的變量,它把工作內存中一個變量的值傳遞給執行引擎。
(6)assign(賦值) : 作用於工作內存的變量,它把一個從執行引擎接收到的值賦給工作內存的變量。
(7)store(存儲) : 作用於工作內存的變量,它把工作內存中一個變量的值傳送到主內存中,以便後續的write操作使用。
(8)write(寫入) : 作用於主內存的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中。

2.3.1 Java內存模型的三大特性 :
(1)原子性 : 由Java內存模型來直接保證的原子性變量操作包括read、load、assign、use、store和read。大致可以認爲,基本數據類型的訪問讀寫是具備原子性的。如若需要更大範圍的原子性,需要synchronized關鍵字約束。(即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行)
(2)可見性 : 可見性是指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。volatile、synchronized、final三個關鍵字可以實現可見性。
(3)有序性 : 如果在本線程內觀察,所有的操作都是有序的;如果在線程中觀察另外一個線程,所有的操作都是無序的。前半句是指"線程內表現爲串行",後半句是指"指令重排序"和"工作內存與主內存同步延遲"現象。

2.4 happens-before原則(先行發生原則)
Java內存模型具備一些先天的“有序性”,即不需要通過任何手段就能夠得到保證的有序性,這個通常也稱爲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()方法的開始。

二、volatile關鍵字

  1. 關鍵字volatile可以說是JVM提供的最輕量級的同步機制,但是它並不容易完全被正確理解和使用。JMM對volatile專門定義了一些特殊的訪問規則。
  2. 當一個變量被volatile修飾後,具有如下兩種特性:
    (1)保證此變量對所有線程的可見性。這裏的"可見性"是指 : 當一條線程修改了這個變量的值,新值對於其他線程來說是可以立即得知的。而普通變量做不到這一點,普通變量的值在線程間傳遞均需要通過主內存來完成。例如:線程A修改一個普通變量的值,然後向主內存進行回寫,另外一條線程B在線程A回寫完成之後再從主內存進行讀取操作,新值纔會對線程B可見。
    需注意:被volatile修飾的變量的運算在併發下是不安全的。原因在於Java裏面的運算並非原子操作。
    tips:
    由於volatile關鍵字只保證可見性,在不符合以下兩條規則的運算場景中,我們仍然需要通過加鎖(synchronized或者lock)來保證原子性。
    a. 運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值
    b. 變量不需要與其他的狀態變量共同參與不變約束

(2)禁止進行指令重排序。
volatile關鍵字禁止指令重排序有兩層意思:
a. 當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作的更改肯定全部已經進行,且結果已經對
後面的操作可見;在其後面的操作肯定還沒有進行;
b. 在進行指令優化時,不能將在對volatile變量訪問的語句放在其後面執行,也不能把volatile變量後面的語句放到其前面執行。

3.volatile的原理和實現機制

前面講述了源於volatile關鍵字的一些使用,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的。

下面這段話摘自《深入理解Java虛擬機》:

“觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的彙編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令”

lock前綴指令實際上相當於一個內存屏障(也成內存柵欄),內存屏障會提供3個功能:
  (1)它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成;
  (2)它會強制將對緩存的修改操作立即寫入主存;
  (3)如果是寫操作,它會導致其他CPU中對應的緩存行無效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章