volatile足以保證資料同步嗎

超人汪小建發表於2017-09-11

在討論之前必須先搞清四種儲存介質:暫存器、高階快取、RAM和ROM。

RAM與ROM大家都比較熟悉了,可以看成是我們經常說的記憶體與硬碟,暫存器屬於處理器裡面的一部分,而高階快取cache是CPU設計者為提高效能引入的一個快取,也可以說是屬於處理器的一部分。在利用CPU進行運算時必定涉及運算元的讀取,假如CPU直接讀取ROM,那麼這個讀取速度簡直是無法忍受的,於是引入了記憶體RAM,這樣做確實讓速度提高了很多,但由於CPU發展十分迅猛而另一方面RAM的發展受到技術及成本的限制發展緩慢,此時產生了一個很難調和的矛盾,CPU運算速度比從RAM讀取資料的速度快了幾個數量級,木桶原理我們都很熟悉了,桶的容量大小取決於最短的那塊,這必將影響處理器的效率,於是又引入了高階快取,直接在CPU新增了幾個級別的快取,他們的速度雖然無法與暫存器比較,但是速度已經提升很多,基本能跟CPU的計算速度相匹配。總結成一句話就是,為了解決CPU運算速度與讀取速度的矛盾,引入了多級儲存機制。

如圖所示,機器的四種儲存介質是有關係的,一般程式執行時會將ROM相關的程式資料都讀進RAM中,而需要運算的資料或運算過程中即將要用到的資料則會被讀進快取記憶體或暫存器中,假如要進行的運算所需要的所有資料及指令都在暫存器和快取記憶體中,則這個運算過程則表現得非常平坦,並不存在效能瓶頸,因為運算速度跟讀取速度基本匹配了。讀取速度快慢的排序如下:暫存器>cache>RAM>ROM,用一個比較好理解但不完全正確的概念來解釋,因為暫存器是離CPU最近的,所以讀取最快,快取記憶體次之,RAM第三,ROM離得最遠,自然速度最慢(當然不能完全用距離來說明這個問題,但用距離是比較好理解的,另外的還因為這些儲存介質的硬體設計不同、工作方式不同)。從另一個角度來看,CPU讀取資料的順序是先嚐試讀暫存器,如果不存在則嘗試讀快取記憶體,如果還不存在則讀RAM,最後才是讀ROM。一些CPU有三級cache,讀取時是一級一級往下直到找到需要的運算元,一般做的比較好的CPU3級快取已經能讓命中率高達95%以上。

有了上面的知識再往下探索就水到渠成了,如果把Java記憶體模型與多級儲存機制類比將發現為了提高效能java引入了工作記憶體的概念,提高了執行緒執行時讀取資料的速度,這樣就可以把java模型中的主存和工作記憶體分別於RAM和快取記憶體或暫存器對應起來,每條執行緒的工作記憶體預先把需要的資料複製到快取記憶體或暫存器(但是不保證所有的工作記憶體的變數副本都是放在快取記憶體,也可能在RAM,具體的還要看JVM是如何實現的),這樣在多執行緒併發時效能得到保證。當然暫存器和快取記憶體由於成本原因存在容量大小限制的問題,這個也是考驗JVM實現的一個難題。

一般引入一種機制解決了一個問題,但同時也會帶來另外一個問題,資料同步即是帶來的另一個問題,即是否能保證當前運算使用的變數值總是當前時刻最新的值。如果變數值並非最新值,將會導致資料的髒讀,最終可能導致計算結果大相徑庭。這時可能有人會想起java中有個volatile關鍵詞,毫無疑問它能保證可見性,讓每個執行緒得到的都是主存中最新的變數值,但它就足以保證資料的同步性了嗎?舉個典型例子,虛擬碼如下:

private volatile int count=0;
private void increase(){count++}
public static void main(String args){
    建立30條執行緒執行;
    每條執行緒任務都是執行10000次increase()方法;
}複製程式碼

執行完所有執行緒任務,我們期望的結果會是30*10000,但實際卻是一個小於30*10000的數,剛開始看到一定覺得有點奇怪,但仔細一想就清楚了,count++編譯後最終並非一個原子操作,它由幾個指令一起組合實現。下圖能較清晰地說明此點,在Java記憶體模型中,count++被分割成5個步驟(當然這個並不是確切的指令執行步驟),這5步不具有原子性,假如在完成過程中,其他執行緒就去讀了主存的count變數,那明顯導致了一個髒讀現象。

導致這個問題的原因其實是因為volatile不具備鎖操作,要解決此問題其實不難,就是將這五步變為原子操作,即保證執行緒一完成之前不能有其他執行緒讀取count變數,對count變數加一個互斥鎖即可達到,執行緒一在執行第①步前對count加鎖,其他執行緒無法對count進行訪問,執行緒一執行完第⑤步後釋放鎖,此刻開始才允許其他執行緒獲取此變數。

Volatile是一個很容易搞混的關鍵詞,很多經驗豐富的開發人員都不能正確使用它,這節從機器結構講到對應的java記憶體模型,再引出主存與工作記憶體之間資料同步的問題,進而更好地解釋了volatile的確切含義——它只保證可見性,它不足以保證資料的同步性。

========廣告時間========

鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。

為什麼寫《Tomcat核心設計剖析》

=========================

歡迎關注:

這裡寫圖片描述
這裡寫圖片描述

相關文章