1. 程式人生 > >Java記憶體模型與happens-before原則

Java記憶體模型與happens-before原則

Java記憶體模型

Java記憶體模型不同於Jvm記憶體模型,Java記憶體模型(JMM)規定了JVM必須遵循一組最小保證,這組保證規定了對變數的寫入操作在何時將於其他執行緒可見。

在Java虛擬機器規範中試圖定義一種Java記憶體模型(Java Memory Model,JMM),JVM通過在適當的位置插入記憶體柵欄來遮蔽JMM和各個硬體平臺和作業系統的記憶體訪問差異,以實現讓Java程式在各種平臺下都能達到一致的記憶體訪問效果。Java記憶體模型並沒有限制執行引擎使用處理器的暫存器或者快取記憶體來提升指令執行速度,也沒有限制編譯器對指令進行重排序。也就是說,在java記憶體模型中,也會存在快取一致性問題和指令重排序的問題。

Java記憶體模型規定所有的變數都是存在主存當中(類似於前面說的實體記憶體),每個執行緒都有自己的工作記憶體(類似於前面的快取記憶體)。執行緒對變數的所有操作都必須在工作記憶體中進行,而不能直接對主存進行操作。並且每個執行緒不能訪問其他執行緒的工作記憶體。
如圖:
imageaf5e572be8aad7ac.png

一,重排序

再沒有充分同步的程式中,如果排程器採用不恰當的方式來交替執行不同執行緒的操作,那麼將導致不正確的結果。更糟糕的是,JMM還使得不同執行緒看到的操作執行順序是不同的,從而導致在缺乏同步的情況下,要推斷操作的執行順序更加複雜,各種使得操作延遲或者砍死亂序執行的不同原因,都可以稱為“重排序”

將快取重新整理到主記憶體的不同時序也可能會導致重排序。

重排序的基礎是前後語句不存在依賴關係時,才有可能發生指令重排序。

記憶體柵欄會遮蔽重排序

兩個操作之間存在happens-before關係,並不意味著Java平臺的具體實現必須要按照happens-before關係指定的順序來執行。如果重排序之後的執行結果,與按happens-before關係來執行的結果一致,那麼這種重排序並不非法(也就是說,JMM允許這種重排序)

二,happens-before

happens-before原則是指如果A和B滿足這個happens-before原則,則可以保證操作B的執行緒可以看到操作A的結果。

當一個變數被多個執行緒讀取並且至少被一個執行緒寫入時,如果在讀操作和寫操作之間沒有按照H-B原則來排序,就會產生資料競爭問題,在正確同步的程式中不存在資料競爭,並且會表現出序列一致性。

1,程式順序原則
單執行緒中,寫在前面的操作A會在寫在後面的操作B之前執行。

2,鎖原則
在鎖上的解鎖操作必須在鎖上的加鎖操作之前執行。

3,volatile變數原則
對volatile變數的寫入操作必須在對該變數的讀操作之前執行。

4,執行緒啟動原則
在一個執行緒上,start操作必須在該執行緒執行任何操作之前執行。

5,執行緒關閉原則
執行緒中任何操作都必須在其他執行緒檢測到該執行緒已經結束之前執行。

6,執行緒中斷原則
當一個執行緒在另一個執行緒呼叫interrupt時,必須在被中斷執行緒檢測到interrupt呼叫之前執行。‘’

7,終結器規則。
物件的建構函式必須在啟動該物件的終結器之前執行完成。

8,傳遞性
如果操作A H-B 操作B,操作B H-B 操作C 則 操作A H-B 操作C

如果2個在不同執行緒的操作不滿足H-B原則,則無法推斷一個操作是否一定在另一個操作之前。

如:

image.png

這裡,在多個執行緒來呼叫時,因為不滿足H-B,所以第一個執行緒呼叫時,已經初始化了resource,沒法保證第二個執行緒來時,獲取到的resource到底是拿到的null還是一個失效值。

三,藉助同步

由於H-B排序功能很強大,因此有時候可以“藉助”現有同步機制的可見性屬性。

“藉助同步”技術是指藉助於現有的H-B原則,來確保物件X的可見性,而不是為了釋出X而建立一個H-B順序。

在類庫中提供的其他H-B順序:
imagefee65956437ebe14.png

四,併發程式設計的三個問題

1,原子性

一個操作要麼全部執行,要麼不執行。

JAVA中原子性靠synchronized和Lock來實現,或者native方法的cas等

2,可見性

多個執行緒訪問一個變數時,一個執行緒修改後其他執行緒可以立即看到這個值的改變。

Java提供了volatile關鍵字來保證可見性

3,有序性

程式執行的順序按照程式碼的先後順序執行。

Java中保證了單執行緒下看起來有序(as if serial),不保證多執行緒下有序。

因為在Java記憶體模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到單執行緒程式的執行,卻會影響到多執行緒併發執行的正確性。

在Java裡面,可以通過volatile關鍵字來保證一定的“有序性”(具體原理在下一節講述)。另外可以通過synchronized和Lock來保證有序性,很顯然,synchronized和Lock保證每個時刻是有一個執行緒執行同步程式碼,相當於是讓執行緒順序執行同步程式碼,自然就保證了有序性。

另外,Java記憶體模型具備一些先天的“有序性”,即不需要通過任何手段就能夠得到保證的有序性,這個通常也稱為 happens-before 原則。如果兩個操作的執行次序無法從happens-before原則推匯出來,那麼它們就不能保證它們的有序性,虛擬機器可以隨意地對它們進行重排序。