Java記憶體模型(MESI、記憶體屏障、volatile和鎖及final記憶體語義)

曹自標發表於2020-12-16

JMM (Java記憶體模型)

Java執行緒的實現

實現執行緒主要有三種方式,Java執行緒從JDK1.3後採用第一種方式實現:

  1. 使用核心執行緒實現(1:1實現)
  2. 使用使用者執行緒實現(1:N實現)
  3. 使用使用者執行緒加輕量級程式混合實現(N:M實現)



  • KTL: 核心執行緒
  • LWP:輕量級程式
  • UT:使用者執行緒
執行緒之間通訊機制

Java併發採用的是共享記憶體模型

  1. 共享記憶體
  2. 訊息傳遞
重排序

包括:

  1. 編譯器優化的重排序。
  2. 指令級並行的重排序。如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。
  3. 記憶體系統的重排序。由於處理器使用快取和讀/寫緩衝區,這使得載入和儲存操作看上去可能是在亂序執行
記憶體屏障

記憶體屏障型別:

StoreLoad Barries的開銷是四種屏障中最大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種記憶體屏障的功能

快取一致性(MESI協議)

https://en.wikipedia.org/wiki/MESI_protocol#Memory_Barriers

狀態:

  • 修改(modified):僅當前處理器擁有該快取行,並且快取行被修改過了,一定時間內會寫回主存,會寫成功狀態會變為S。
  • 獨佔(exclusive):僅當前處理器擁有該快取行,並且沒有修改過,是最新的值。
  • 共享(share):有多個處理器擁有該快取行,每個處理器都沒有修改過快取,是最新的值。
  • 失效(invalid):快取行被其他處理器修改過,該值不是最新的值,需要讀取主存上最新的值。
MESI M E S I
M X X X
E X X X
S X X
I

這個圖的含義就是當一個core持有一個cacheline的狀態為Y時,其它core對應的cacheline應該處於狀態X, 比如地址 0x00010000 對應的cacheline在core0上為狀態M, 則其它所有的core對應於0x00010000的cacheline都必須為I

狀態轉換:

  • Red: Bus initiated transaction.
  • Black: Processor initiated transactions.

處理器請求:

  • PrRd: 處理器請求讀Cache block
  • PrWr: 處理器請求寫Cache block

匯流排請求:

  • BusRd:由一個處理器向另一個處理器發出的快取讀請求
  • BusRdX:由一個快取中沒有該變數的處理器向另一個處理器傳送的快取寫請求
  • BusUpgr:由一個快取中擁有該變數的處理器向另一個處理器傳送的快取寫請求
  • Flush:有一個處理器將變數寫回了主存
  • FlushOpt:通知別的處理器修改變數的值(快取間傳值)


示例:

Happens-Before
  • 某個執行緒中的每個動作都happens-before該執行緒中該動作後面的動作
  • 某個管程上的unlock動作happens-before同一個管程上後續的lock動作
  • 對某個volatile欄位的寫操作happens-before每個後續對該volatile欄位的讀操作
  • 在某個執行緒物件上呼叫start()方法happens-before該啟動了的執行緒中的任意動作
  • 某個執行緒中的所有動作happens-before任意其它執行緒成功從該執行緒物件上join()中返回
  • 如果某個動作a happens-before動作b,且b happens-before動作c,則有a happens-before c
volatile欄位記憶體語義

happens-before規則

對某個volatile欄位的寫操作happens-before每個後續對該volatile欄位的讀操作

特性:

  • 可見性: 對任意單個volatile變數的讀,總是能看到(任意執行緒)對該volatile最後的寫入
  • 原子性: 對任意單個volatile變數的讀/寫具有原子性,但類似與volatile++這種複合操作不具有原子性

記憶體語義

當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數值重新整理到主記憶體
記憶體語義的實現:volatile修飾的變數,會多一個lock指令,這個操作的作用相當於一個記憶體屏障。
保守策略的JMM記憶體屏障插入策略:

  • 在每個volatile寫操作的前面插入一個StoreStore屏障
  • 在每個volatile寫操作的後面插入一個StoreLoad屏障
  • 在每個volatile讀操作的後面插入一個LoadLoad屏障
  • 在每個volatile讀操作的後面插入一個LoadStore屏障

鎖的記憶體語義

happens-before規則

某個管程上的unlock動作happens-before同一個管程上後續的lock動作

記憶體語義

當執行緒釋放鎖時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體中。

ReentrantLock

CAS實現
synchronized
ACC_SYNCHRONIZED方法訪問標識或monitor enter、monitor exit指令實現

final欄位記憶體語義

兩個排序規則:

  1. 在建構函式內對一個final域的寫入,與隨後把這個被構造物件的引用賦值給一個引用變數,這兩個操作之間不能重排序
  • JMM禁止編譯器把final域的寫重排序到建構函式之外
  • 編譯器會在final域的寫之後,建構函式return之前,插入一個storestore屏障,這個屏障禁止處理器把final域的寫重排序到建構函式之外。
  1. 初次讀一個包含final域的物件的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序
JSR-133對舊記憶體模型的修補
  1. 增強volatile的記憶體語義。舊記憶體模型執行volatile變數與普通變數重排序。JSR-133嚴格限制volatile變數與普通變數的重排序。
  2. 增強final的記憶體語義。在舊記憶體模型,多次讀取同一個final變數的值可能會不同。為此,JSR-133為final增加了兩個重排序規則,在保證fianl引用不會從建構函式內逃逸出的情況下,final具有了初始化安全性。

參考:

相關文章