筆者去年面試過幾家公司,基本上每家公司都會問到volatile,甚至有的公司每輪面試的時候都會問到。面試官這麼喜歡問volatile就是因為這個關鍵字涉及到的知識點較多比如Java記憶體模型、記憶體屏障、happen-befor等知識,可以繼續挖掘到系統指令、超執行緒等知識。
Java記憶體模型(JMM)
volatile是Java虛擬機器提供的最輕量的同步機制,但很難被正確的理解與使用,通過學習Java記憶體模型對volatile專門定義的一些特殊訪問規則,或許會對理解volatile有一定幫助。
Java記憶體模型定義了執行緒和記憶體之間關係:執行緒之間的共享變數儲存在主記憶體中,每個執行緒都有一個私有的本地記憶體,本地記憶體中儲存了該執行緒以讀 / 寫共享變數的副本。本地記憶體是 JMM 的一個抽象概念,並不真實存在;它涵蓋記憶體、快取、暫存器以及其他的硬體和編譯器優化。Java的記憶體模型抽象如下:
volatile的語義
volatile主要提供了兩種語義:
1,可見性:
可見性是指一個執行緒寫入的值,其他執行緒能夠立即讀取。在由Java記憶體模型可知道,每個執行緒都是有本地記憶體。所以執行緒A寫入在正常情況下,執行緒B不能立即讀取。但是在volatile變數,可以保證執行緒A不寫入本地記憶體直接寫入主記憶體,執行緒B直接從主記憶體中讀取,不從本地記憶體中讀取。
2,禁止指令重排序:
重排序是指編譯器和處理器為了優化程式效能而對指令進行重排序的一種優化手段。
Java程式的幾種重排序
編譯器優化重排序:編譯器在不改變單執行緒程式語義的前提下,可以重新安排語句的執行順序。 指令級並行的重排序:如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。 記憶體系統的重排序:處理器使用快取和讀寫緩衝區,這使得載入和儲存操作看上去可能是在亂序執行 volatile的技術基石--記憶體屏障
記憶體屏障是cpu指令,該指令保證特定操作的順序性和某些記憶體的可見性。插入一條記憶體屏障指令之後會告訴編譯器和CPU:不管什麼指令都不能和這條指令重排序。 記憶體屏障所做的另外一件事情就是強制刷出各種CPU cache, 如一個Write-Barrier(寫入屏障)將刷出所有在Barrier之前寫入cache的資料,因此,任何CPU上的執行緒都能讀取到這些資料的最新版本。
對於Java程式而言, 如果把加入volatile關鍵字的程式碼和未加入volatile關鍵字的程式碼都生成彙編程式碼,會發現加入volatile關鍵字的程式碼會多出一個lock字首指令。
volatile的典型用例
狀態標誌,程式碼示例如下:
執行緒1執行run()的過程中,可能有另外的執行緒2呼叫了shutdown,所以stop變數必須是volatile(利用的volatile的可見性)。
還有一種常見的用法在雙重檢驗的單例實現上,程式碼如下:
instance = new Singleton()這句,這並非是一個原子操作,事實上在 JVM 中這句話大概做了下面 3 件事情:
給 instance 分配記憶體 呼叫 Singleton 的建構函式來初始化成員變數 將instance物件指向分配的記憶體空間(執行完這步 instance 就為非 null 了) 如果 instance變數沒有加volatile, 因為指令重排序的存在,就可能導致執行步驟是1-2-3,也可能是1-3-2。一旦是1-3-2,就可能會導致訪問未初始化的記憶體。但是加上 volatile關鍵字之後,一定保證是按照1-2-3步驟執行的(利用的 volatile的禁止重排序 )。
歡迎工作一到五年的Java工程師朋友們加入Java架構師:697558955
群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!