1. 程式人生 > >volatile和鎖的記憶體語義與實現

volatile和鎖的記憶體語義與實現

1.volatile的記憶體語義與實現

1.1 volatile寫讀的記憶體語義

在介紹鎖的記憶體語義之前,我們先簡單介紹一下volatile寫讀的記憶體語義:

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

這兩條保證了volatile能夠達到它的即時可見的特性。
那JMM如何能夠保證volatile實現其記憶體語義的,簡單來說就是通過記憶體屏障。如果看過volatile變數彙編後的指令程式碼就會在程式碼中發現一句:

lock add1 $0x0

它的簡單含義就是要把工作記憶體中的共享變數值重新整理到主記憶體中,相當於加入記憶體屏障。

1.2 volatile有序的記憶體語義

volatile的另一個特性是禁止指令重排序,這裡的記憶體語義我們可以總結為:

  • volatile讀之後 的操作不會被重排序到 volatile讀之前
  • volatile寫之前 的操作不會被重排序到 volatile寫之後
  • 先volatile寫–後volatile讀,不可重排序

JMM通過插入記憶體屏障來實現以上語義,實質上有四種記憶體屏障策略:

  • volatile寫操作前插入StoreStore屏障
  • volatile寫操作後插入StoreLoad屏障
  • volatile讀操作前插入LoadLoad屏障
  • volatile讀操作後插入LoadStore屏障

其中StoreLoad屏障是全能型屏障,可以完成其他3個屏障的功能。所以它被大部分CPU支援。不同CPU有著不同的重排序規則,但是這一套JMM屏障策略可以完成所有型別CPU下的volatile語義。例如對於x86的CPU,它本身只支援寫-讀操作的重排序,對讀-讀,讀-寫,寫-寫操作的重排序都不支援;那麼我們只需要加入StoreLoad屏障來避免寫-讀操作的重排序即可實現volatile語義。

2.鎖的記憶體語義與實現

理解了volatile的記憶體語義,鎖的記憶體語義就會好理解了。

  • 當執行緒獲取鎖時,JMM會把執行緒對應的本地記憶體置為無效,然後臨界區的程式碼從主存中讀入共享變數到工作記憶體。
  • 當執行緒釋放鎖時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體中。

看完上面鎖的記憶體語義,是不是感覺和volatile的記憶體語義很相像。對比鎖和volatile的記憶體語義:

  • 鎖的獲取和volatile的讀有相同的記憶體語義
  • 鎖的釋放和volatile的寫有相同的記憶體語義

參考: