Java多執行緒之記憶體可見性實現方式
阿新 • • 發佈:2019-01-27
可見性的實現方式
Java語言層面支援的可見性實現方式:
- synchronized
- volatile
synchronized實現可見性原理
synchronized可以實現:
- 原子性(同步)
- 可見性
JMM關於synchronized的兩條規定:
- 執行緒解鎖前,必須把共享變數的最新值到主記憶體中。
- 執行緒加鎖前,將清空工作記憶體中共享變數的值,從而使用共享變數時需要從主記憶體中重新讀取最新的值(注意:加鎖與解鎖需要是同一把鎖)
保證了執行緒在解鎖之前對共享變數的修改在下一次加鎖時對其他執行緒可見
執行緒執行互斥程式碼的過程
- 獲得互斥鎖
- 清空工作記憶體
- 從主記憶體拷貝變數的最新副本到工作記憶體
- 執行程式碼
- 將更改後的共享變數的值重新整理到主記憶體
- 釋放互斥鎖
導致共享變數線上程間不可見的原因:
- 執行緒的交叉執行
- 重排序結合線程交叉執行
- 共享變數更新後的值沒有在工作記憶體與主記憶體間及時更新
synchronized解決方案
- 加鎖操作使鎖內程式碼在一段時間內只能由一個執行緒來執行,只有噹噹前執行緒釋放了鎖,其他執行緒才有機會去執行這段程式碼。保證了鎖內程式碼的原子性,避免了執行緒在鎖內部交叉執行。
- 因為synchronized避免了執行緒在鎖內部交叉執行,所以無論怎樣重排序都是在單獨的執行緒裡執行的,不會對結果造成影響。
- synchronized通過兩條可見性規範來避免的
volatile實現可見性
volatile關鍵字:
- 能夠保證volatile變數的可見性
- 不能保證volatile變數複合操作的原子性
volatile如何實現記憶體可見性
深入來講,通過加入記憶體屏障和禁止重排序優化來實現的。
- 對volatile變數執行寫操作時,會在寫操作後加入一條store屏障指令
- 對volatile變數執行讀操作時,會在寫操作後加入一條load屏障指令
通俗來講,volatile變數在每次被執行緒訪問時,都強迫從主記憶體中重讀該變數的值,而當該變數發生變化時,又會強迫執行緒將最新的值重新整理到主記憶體。這樣任何時刻,不同的執行緒總能看到該變數的最新值。
執行緒寫volatile變數的過程:
- 改變執行緒工作記憶體中volatile變數副本的值
- 將改變後的副本的值從工作記憶體中重新整理到主記憶體
執行緒讀volatile變數的過程:
- 從主記憶體中讀取volatile變數的最新值到執行緒的工作記憶體中
- 從工作記憶體中讀取volatile變數的副本
volatile適用場合
要在多執行緒中安全的使用volatile變數,必須同時滿足- 對變數的寫入操作不依賴其當前值
- 不滿足:number++、count = count * 5
- 滿足:boolean 變數、記錄溫度變化的變數等
- 該變數沒有包含在具有其他變數的不變式中
- 不滿足:不變式 low < up
- 對變數的寫入操作不依賴其當前值
synchronized和volatile比較
- volatile不需要加鎖,比synchronized更輕量級,不會阻塞執行緒
- 從記憶體可見性角度看,volatile讀相當於加鎖,volatile寫相當於寫鎖
- synchronized既能保證可見性,又能保證原子性。volatile只能保證可見性,無法保證原子性