關鍵字——Volatile

  • 保證了多線程環境下共享變量的可見性
  • 禁止指令重排序

現代計算機的內存模型:

在早期計算機中cpu和內存的速度是差不多的,但是在現代計算機中,cpu的指令速度遠超內存的存取速度,由於計算機的存儲設備與處理器的運算速度有幾個數量級的差距,所以現代計算機系統加入一層讀寫速度儘可能接近處理器運算速度的**高速緩存(Cache)**來作爲內存與處理器之間的緩衝。

高速緩存解決了處理器與內存的速度矛盾,但是也引入了一個新的問題:緩存一致性問題(CacheCoherence);

在多處理器系統中,每個處理器都有自己的高速緩存,而它們又共享同一主內存(MainMemory)。

MESI(緩存一致性協議):CPU寫數據時,若發現操作的變量是共享變量,就會通知其他CPU將該變量的緩存行置爲無效狀態,其他CPU再次讀的時候,發現無效,就去主內存中讀取。

Java內存模型(JMM)

Java內存模型(JMM),JVM規範中所定義的一種內存模型,以屏蔽所有類型的硬件和操作系統內存訪問差異,讓Java程序在不同的平臺上能夠達到一致的內存訪問結果

JMM描述了Java程序中各種變量的訪問規則,以及在JVM找那個將變量,存儲到內存和從內存中讀取變量這樣的底層細節。規則如下:

  • 所有的共享變量都存儲於主內存(實例變量和類變量,不包括局部變量,局部變量是線程私有的,不存在競爭問題)
  • 每一個線程存在於自己的工作內存,其中保存了主內存中線程使用到的變量的副本
  • 線程對變量的讀寫等操作都必須在工作內存中完成,不能直接讀寫主內存中的變量
  • 不同的線程不能直接訪問對方工作內存中的變量,線程間變量的值的傳遞需要通過主內存中轉來完成

指令重排序

爲了提高性能,編譯器和處理器常常會對既定的代碼的執行順序進行指令重排序;

java編譯器會在生成指令系列時在適當的位置插入內存屏障指令來禁止特定類型的處理器重排序;volatile寫操作時在前後分別插入內存屏障(StoreStore屏障),而讀操作僅在後面插入兩個內存屏障(LoadLoad屏障、LoadStore屏障)

一個操作執行的結果需要對另一個操作可見,那麼兩個操作之間必須存在happens-before關係

總結:面試官問你對volatile的理解:

1.需要先講講java的內存模型,java的共享變量都存儲在主內存當中,每當有一個線程需要讀取內存中的變量的時候,JVM會將主內存中的變量拷貝一份放入線程的工作內存中,多個線程之間並不可見,如果我們要保證可見性,就得使用volatile關鍵字;
2.volatile可以保證共享變量的可見性,但volatile並不保證變量的原子性,如果要保證變量的原子性,我們可以使用synchronized關鍵字或者atomic類
3.同時volatile可以禁止指令重排,因爲編譯器和處理器會對我們的代碼進行重排序實現性能優化,在一些數據沒有依賴性的時候,我們的代碼執行順序可能不是我們想象的那樣,在多線程情況下,可能會造成一些奇怪的錯誤,volatile禁止指令重排的底層是使用了一個叫內存屏障的cpu指令,通過在適當的位置插入內存屏障從而禁止指令重排序,同時會強制刷新cpu緩存

簡而言之:volatile是一個java虛擬機提供的輕量級的同步機制,保證可見性和禁止指令重排,但不保證原子性。

參考文獻:
https://juejin.im/post/5d9c8ab4518825094e372706
https://juejin.im/post/5ea913d35188256d4576d199

還有就是volatile問題可以延伸到單例模式中的使用,有興趣的童鞋可以去深入瞭解一下~~~
設計模式之單例模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章