Java內存模型(MESI、內存屏障、volatile和鎖及final內存語義)

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協議)

//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具有了初始化安全性。

參考: