閱讀173 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java並發——volatile的原理

volatile關鍵字就是Java中提供的另一種解決可見性和有序性問題的方案。對於原子性,需要強調一點,也是大家容易誤解的一點:對volatile變量的單次讀/寫操作可以保證原子性的,如long和double類型變量,但是並不能保證i++這種操作的原子性,因為本質上i++是讀、寫兩次操作。

1 volatile的原理

1.1 可見性

在前文中已經提及過,線程本身並不直接與主內存進行數據的交互,而是通過線程的工作內存來完成相應的操作。這也是導致線程間數據不可見的本質原因。因此要實現volatile變量的可見性,直接從這方麵入手即可。對volatile變量的寫操作與普通變量的主要區別有兩點:

  • 修改volatile變量時會強製將修改後的值刷新的主內存中
  • 修改volatile變量後會導致其他線程工作內存中對應的變量值失效。因此,再讀取該變量值的時候就需要重新從讀取主內存中的值。

通過這兩個操作,就可以解決volatile變量的可見性問題。

1.2 原子性

volatile隻能保證對單次讀/寫的原子性。因為long和double兩種數據類型的操作可分為高32位和低32位兩部分,因此普通的long或double類型讀/寫可能不是原子的。因此,鼓勵大家將共享的long和double變量設置為volatile類型,這樣能保證任何情況下對long和double的單次讀/寫操作都具有原子性。

1.3 順序性

在解釋這個問題前,我們先來了解一下Java中的happen-before規則:如果a happen-before b,則a所做的任何操作對b是可見的。

JSR 133中定義的happen-before規則有:

  • 同一個線程中的,前麵的操作 happen-before 後續的操作。(即單線程內按代碼順序執行。但是,在不影響在單線程環境執行結果的前提下,編譯器和處理器可以進行重排序,這是合法的。換句話說,這一是規則無法保證編譯重排和指令重排)
  • 監視器上的解鎖操作 happen-before 其後續的加鎖操作。(Synchronized 規則)
  • 對volatile變量的寫操作 happen-before 後續的讀操作。(volatile 規則)
  • 線程的start() 方法 happen-before 該線程所有的後續操作。(線程啟動規則)
  • 線程所有的操作 happen-before 其他線程在該線程上調用 join 返回成功後的操作
  • 如果 a happen-before b,b happen-before c,則a happen-before c(傳遞性)

1.4 內存屏障

為了實現volatile可見性和happen-befor的語義。JVM底層是通過一個叫做“內存屏障”的東西來完成。內存屏障,也叫做內存柵欄,是一組處理器指令,用於實現對內存操作的順序限製。下麵是完成上述規則所要求的內存屏障:

  • LoadLoad 屏障

執行順序:Load1—>Loadload—>Load2

確保Load2及後續Load指令加載數據之前能訪問到Load1加載的數據。

  • StoreStore 屏障

執行順序:Store1—>StoreStore—>Store2

確保Store2以及後續Store指令執行前,Store1操作的數據對其它處理器可見。

  • LoadStore 屏障

執行順序: Load1—>LoadStore—>Store2

確保Store2和後續Store指令執行前,可以訪問到Load1加載的數據。

  • StoreLoad 屏障

執行順序: Store1—> StoreLoad—>Load2

確保Load2和後續的Load指令讀取之前,Store1的數據對其他處理器是可見的。

最後更新:2017-07-26 09:04:55

  上一篇:go  【Linux shell】tar命令排除某些文件打包
  下一篇:go  Java並發——線程間協作(wait、notify、sleep、yield、join)