一個工作了6年的Java程式設計師,在阿里二面,被問到“volatile”關鍵字。
然後,就沒有然後了…
同樣,另外一個去美團面試的工作4年的小夥伴,也被“volatile關鍵字“。
然後,也沒有然後了…
這個問題說實話,是有點偏底層,但也的確是併發程式設計裡面比較重要的一個關鍵字。
下面,我們來看看普通人和高手對於這個問題的回答吧。
普通人:
嗯… volatile可以保證可見性。
高手:
volatile關鍵字有兩個作用。
- 可以保證在多執行緒環境下共享變數的可見性。
- 通過增加記憶體屏障防止多個指令之間的重排序。
我理解的可見性,是指當某一個執行緒對共享變數的修改,其他執行緒可以立刻看到修改之後的值。
其實這個可見性問題,我認為本質上是由幾個方面造成的。
-
CPU層面的快取記憶體,在CPU裡面設計了三級快取去解決CPU運算效率和記憶體IO效率問題,但是帶來的就是快取的一致性問題,而在多執行緒並行執行的情況下,快取一致性就會導致可見性問題。
所以,對於增加了volatile關鍵字修飾的共享變數,JVM虛擬機器會自動增加一個#Lock彙編指令,這個指令會根據CPU型號自動新增匯流排鎖或/快取鎖
我簡單說一下這兩種鎖,
- 匯流排鎖是鎖定了CPU的前端匯流排,從而導致在同一時刻只能有一個執行緒去和記憶體通訊,這樣就避免了多執行緒併發造成的可見性。
- 快取鎖是對匯流排鎖的優化,因為匯流排鎖導致了CPU的使用效率大幅度下降,所以快取鎖只針對CPU三級快取中的目標資料加鎖,快取鎖是使用MESI快取一致性來實現的。
-
指令重排序,所謂重排序,就是指令的編寫順序和執行順序不一致,在多執行緒環境下導致可見性問題。指令重排序本質上是一種效能優化的手段,它來自於幾個方面。
- CPU層面,針對MESI協議的更進一步優化去提升CPU的利用率,引入了StoreBuffer機制,而這一種優化機制會導致CPU的亂序執行。當然為了避免這樣的問題,CPU提供了記憶體屏障指令,上層應用可以在合適的地方插入記憶體屏障來避免CPU指令重排序問題。
- 編譯器的優化,編譯器在編譯的過程中,在不改變單執行緒語義和程式正確性的前提下,對指令進行合理的重排序優化來提升效能。
所以,如果對共享變數增加了volatile關鍵字,那麼在編譯器層面,就不會去觸發編譯器優化,同時再JVM裡面,會插入記憶體屏障指令來避免重排序問題。
當然,除了volatile以外,從JDK5開始,JMM就使用了一種Happens-Before模型去描述多執行緒之間的記憶體可見性問題。
如果兩個操作之間具備Happens-Before關係,那麼意味著這兩個操作具備可見性關係,不需要再額外去考慮增加volatile關鍵字來提供可見性保障。
以上就是我對這個問題的理解。
總結
在我看來,併發程式設計是每個程式設計師必須要掌握好的領域,它裡面涵蓋的設計思想、和併發問題的解決思路、以及作為一個併發工具,都是非常值得深度研究的。
我推薦大家去讀一下《Java併發程式設計深度解析與原理實戰》這本書,對Java併發這塊的內容描述得很清晰。
好的,本期的普通人VS高手面試系列就到這裡結束了,喜歡的朋友記得點贊和收藏。
另外,有任何技術上的問題,職業發展有關的問題,都可以私信我,我會在第一時間回覆。
版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自
Mic帶你學架構
!
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術乾貨!