【面試普通人VS高手系列】volatile關鍵字有什麼用?它的實現原理是什麼?

跟著Mic學架構發表於2022-04-29

一個工作了6年的Java程式設計師,在阿里二面,被問到“volatile”關鍵字。

然後,就沒有然後了…

同樣,另外一個去美團面試的工作4年的小夥伴,也被“volatile關鍵字“。

然後,也沒有然後了…

這個問題說實話,是有點偏底層,但也的確是併發程式設計裡面比較重要的一個關鍵字。

下面,我們來看看普通人和高手對於這個問題的回答吧。

普通人:

嗯… volatile可以保證可見性。

高手:

volatile關鍵字有兩個作用。

  1. 可以保證在多執行緒環境下共享變數的可見性。
  2. 通過增加記憶體屏障防止多個指令之間的重排序。

我理解的可見性,是指當某一個執行緒對共享變數的修改,其他執行緒可以立刻看到修改之後的值。

其實這個可見性問題,我認為本質上是由幾個方面造成的。

  1. CPU層面的快取記憶體,在CPU裡面設計了三級快取去解決CPU運算效率和記憶體IO效率問題,但是帶來的就是快取的一致性問題,而在多執行緒並行執行的情況下,快取一致性就會導致可見性問題。

    image-20220328161249113

    所以,對於增加了volatile關鍵字修飾的共享變數,JVM虛擬機器會自動增加一個#Lock彙編指令,這個指令會根據CPU型號自動新增匯流排鎖或/快取鎖

    我簡單說一下這兩種鎖,

    • 匯流排鎖是鎖定了CPU的前端匯流排,從而導致在同一時刻只能有一個執行緒去和記憶體通訊,這樣就避免了多執行緒併發造成的可見性。
    • 快取鎖是對匯流排鎖的優化,因為匯流排鎖導致了CPU的使用效率大幅度下降,所以快取鎖只針對CPU三級快取中的目標資料加鎖,快取鎖是使用MESI快取一致性來實現的。
  2. 指令重排序,所謂重排序,就是指令的編寫順序和執行順序不一致,在多執行緒環境下導致可見性問題。指令重排序本質上是一種效能優化的手段,它來自於幾個方面。

    • CPU層面,針對MESI協議的更進一步優化去提升CPU的利用率,引入了StoreBuffer機制,而這一種優化機制會導致CPU的亂序執行。當然為了避免這樣的問題,CPU提供了記憶體屏障指令,上層應用可以在合適的地方插入記憶體屏障來避免CPU指令重排序問題。
    • 編譯器的優化,編譯器在編譯的過程中,在不改變單執行緒語義和程式正確性的前提下,對指令進行合理的重排序優化來提升效能。

    所以,如果對共享變數增加了volatile關鍵字,那麼在編譯器層面,就不會去觸發編譯器優化,同時再JVM裡面,會插入記憶體屏障指令來避免重排序問題。

當然,除了volatile以外,從JDK5開始,JMM就使用了一種Happens-Before模型去描述多執行緒之間的記憶體可見性問題。

如果兩個操作之間具備Happens-Before關係,那麼意味著這兩個操作具備可見性關係,不需要再額外去考慮增加volatile關鍵字來提供可見性保障。

以上就是我對這個問題的理解。

總結

在我看來,併發程式設計是每個程式設計師必須要掌握好的領域,它裡面涵蓋的設計思想、和併發問題的解決思路、以及作為一個併發工具,都是非常值得深度研究的。

我推薦大家去讀一下《Java併發程式設計深度解析與原理實戰》這本書,對Java併發這塊的內容描述得很清晰。

好的,本期的普通人VS高手面試系列就到這裡結束了,喜歡的朋友記得點贊和收藏。

另外,有任何技術上的問題,職業發展有關的問題,都可以私信我,我會在第一時間回覆。

file

版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Mic帶你學架構
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術乾貨!

相關文章