1. 程式人生 > >《Java併發程式設計的藝術》讀書筆記——Java記憶體模型

《Java併發程式設計的藝術》讀書筆記——Java記憶體模型

第三章 Java記憶體模型

3.1 記憶體模型基礎

3.1.1 併發程式設計的兩個關鍵問題

  • 執行緒之間如何通訊
    java採用共享記憶體模型隱式通訊
  • 執行緒之間如何同步
    共享記憶體模型模型需要顯式指定同步

3.1.2 記憶體模型抽象結構

記憶體模型抽象結構

3.1.3 從原始碼到指令序列的重排序

從原始碼到指令序列的重排序

3.1.5 happens-before

JSR-133記憶體模型採用了happens-before概念,指前一個操作執行的結果對後一個操作可見,且前一個操作按順序排在後一個操作之前。但執行次序不一定。另外,這兩個操作可以在在同一執行緒也可以不在。
happens-before常用規則
- 程式順序規則
同一執行緒的操作順序
- 監視器鎖規則
一個鎖的解鎖h-b加鎖
- volatile變數規則
volatile的寫h-b讀
- 傳遞性

3.2重排序

3.2.1資料依賴性

兩操作訪問同一變數且其中一操作為寫操作。
編譯器和處理器重排序不能改變存在資料依賴關係的兩操作的執行結果(編譯器和處理器只考慮單處理器和單執行緒裡的資料依賴性)

3.2.2 as-if-serial

重排序不影響單執行緒程式執行結果

3.2.4 重排序與多執行緒

重排序可能會破壞多執行緒的語義,破壞資料依賴性和控制依賴性(處理器猜測執行,提前讀取計算結果儲存在重排序緩衝,然後判斷後寫入變數)

3.3 順序一致性

如果多執行緒程式正確同步,則程式的執行具有順序一致性。即程式執行結果和它在順序一致性記憶體模型裡的執行結果相同。
這裡的同步廣義,包括常用原語synchronized volatile final

3.4 volatile

3.4.1 volatile的特性

-可見性 對一個volatile的讀,總能看到任意執行緒對這個volatile變數最後的寫入
-原子性 對任意單個volatile變數的讀/寫具有原子性,但是volatile++沒有原子性

3.4.3 volatile的寫-讀的記憶體語義

  • 寫的記憶體語義:寫一個volatile變數時,JMM(java記憶體模型)會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體
  • 讀的記憶體語義:讀一個volatile變數時,JMM會把該執行緒對應的本地記憶體置為無效,執行緒接下來將從主記憶體中讀取共享變數

合在一起,執行緒B讀一個volatile變數後,執行緒A寫volatile變數之前的所有可見共享變數的值均對執行緒B可見
volatile寫和鎖釋放具有相同的記憶體語義,volatile讀和鎖獲取具有相同的記憶體語義

3.4.4 volatile的記憶體語義的實現

  • 如果第二個操作是volatile寫,不能重排序(volatile寫之前插入StoreStore屏障)
  • 如果第一個操作是volatile讀,不能重排序(volatile讀之後插入LoadStore屏障)
  • 如果第一個操作是volatile寫,第二個操作是volatile讀,不能重排序(volatile寫之後插入StoreLoad屏障,volatile讀之前插入LoadLoad屏障)

3.5 鎖的記憶體語義

3.5.1 鎖釋放獲取的記憶體語義

一個鎖的解鎖happens-before於隨後對它的加鎖
執行緒釋放鎖時JMM會將該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體
執行緒獲取鎖時,JMM會把該執行緒對應的本地記憶體置為無效,然後從主記憶體讀取共享變數
volatile寫和鎖釋放具有相同的記憶體語義,volatile讀和鎖獲取具有相同的記憶體語義

3.5.2 鎖記憶體語義的實現

以重入鎖ReentrantLock為例,它包括公平鎖和非公平鎖
ReentrantLock依賴於java同步器框架AQS,後者使用volatile來維護同步狀態
- 公平鎖和非公平鎖釋放時都要寫volatile變數
- 公平鎖獲取時,首先volatile讀
- 非公平鎖獲取時,首先用compareAndSet更新volatile變數,這個CAS操作同時具有volatile讀和volatile寫的記憶體語義。
原因:CAS的處理器程式碼中,如果是多處理器,就為cmpxchg指令加上lock字首
* lock通過快取鎖定的方式保證讀改寫指令執行的原子性
* lock禁止該指令和前後的讀寫指令的重排序
* lock把寫緩衝區的所有資料重新整理到記憶體裡