最近工做中又用到了volatile關鍵字,一直以來就是單純的使用,也沒有仔細看過相關內容,此次藉機會詳細的整理了下有關volatile的資料,記錄在案以備查閱。html
首先咱們來看一個小例子:java
1 public class VolatileDemo1 { 2 private boolean flag = true; 3 4 public static void main(String[] args) throws InterruptedException { 5 VolatileDemo1 demo = new VolatileDemo1(); 6 Thread thread = new Thread(() -> { 7 long start = System.currentTimeMillis(); 8 while (demo.flag) { 9 } 10 long end = System.currentTimeMillis(); 11 System.out.println("終止了while循環!flag的值爲:" + demo.flag); 12 System.out.println("耗時:" + ( end -start )); 13 }); 14 thread.start(); 15 TimeUnit.SECONDS.sleep(2); 16 demo.flag = false; 17 } 18 }
這段代碼是volatile關鍵字的典型應用場景之一,兩個線程(主線程與thread 線程)經過共享一個變量進行信息交互,在上一段代碼中,因爲沒有爲flag變量加上volatile關鍵字,能夠預見,線程thread中的while循環並不會跳出。那麼,是否是隻有加volatile關鍵字能夠解決這個問題呢?或者說咱們能不能不改動代碼就達到目的(主線程中改變flag的值後,thread線程能夠讀到,使while循環能夠跳出)。答案固然是能夠的,咱們能夠採用如下方式:(如圖)程序員
在虛擬機參數選項上加上-Xint(請注意,這個參數在JDK1.8版本及以上),一樣可以使while循環跳出,那麼這個-Xint參數到底有什麼做用呢?請看如下截圖:編程
這張截圖來自Oracle的Java HotSpot VM Options 官方文檔,翻譯過來的意思是:「以純解釋模式運行應用程序。禁用編譯到本機代碼,全部字節碼由解釋器執行。在這種模式下,just in time (JIT)編譯器所提供的性能優點並不存在。」這麼說只要是禁止了JIT即時編譯,就起到了和加volatile同樣的做用,那他們兩個有什麼區別嗎?JIT即時編譯又作了什麼呢?請繼續往下看。windows
要扯明白上面的問題,咱們還要說下Java的內存模型,首先什麼是內存模型呢?周所周知,在現代計算機硬件系統的不斷改進中,CPU和內存之間的多級緩存機制致使的緩存一致性問題,以及爲了高效執行代碼而進行的處理器優化和指令重排序問題,是併發編程中的可見性、原子性、有序性問題的硬件層面緣由。在併發編程中,爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。經過這些規則來規範對內存的讀寫操做,從而保證指令執行的正確性。它與處理器有關、與緩存有關、與併發有關、與編譯器也有關。他解決了CPU多級緩存、處理器優化、指令重排等致使的內存訪問問題,保證了併發場景下的一致性、原子性和有序性。緩存
說的直白點,內存模型就是解決多線程場景下併發問題的一個重要規範,而不一樣的編程語言對於這個規範,在實現上可能有所不一樣,而Java內存模型(Java Memory Model ,JMM)就是Java編程語言提供的一種符合內存模型規範的,屏蔽了各類硬件和操做系統的訪問差別的,保證了Java程序在各類平臺下對內存的訪問都能保證效果一致的機制及規範。同時Java中提供了一系列和併發處理相關的關鍵字,好比volatile
、synchronized
等,其實這些關鍵字就是對Java內存模型規範的一種實現,他們封裝了Java內存模型規範底層的實現後提供給程序員使用,用來解決Java併發編程問題。多線程
有了上面的對於內存模型的描述,那麼咱們就很好理解了,若是要解決併發編程中的問題,最簡單直接的作法就是不使用處理器執行代碼的優化技術、不使用指令重排序、不使用CPU緩存等等優化技術。可是,這麼作顯然就是因噎廢食了。而咱們使用的-Xint參數,根據官網的的描述(沒有JIT編譯的部分,所有是由解釋器解釋執行)和最終的執行結果看,咱們能夠推論出:-Xint參數的做用在廢止JIT即時編譯應該也就是廢除了指令重排序和CPU緩存等優化技術,這裏我沒有深刻的研究過這個參數和JIT,只是臨時應用,因此作出的推論徹底是根據官網的的描述和代碼的執行結果看,不必定徹底正確,若是有了解的大神,還請不吝賜教。併發
那下面咱們就來看看volatile關鍵字,顯然volatile關鍵字不會和-Xint同樣因噎廢食,全面封殺優化技術,那他是怎麼作的呢?oracle
首先內存模型解決併發問題主要採用兩種方式:限制處理器優化和使用內存屏障,volatile做爲內存模型規範的一種應用實現方式,天然也是實現這兩種方式。jvm
對於volatile變量,生成的彙編代碼在volatile修飾的共享變量進行寫操做的時候會多出一個Lock前綴的指令,將這個緩存中的變量回寫到系統主存中。
lock前綴指令實際上至關於一個內存屏障(也稱內存柵欄),內存屏障會提供3個功能:
1)它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經所有完成;
2)它會強制將對緩存的修改操做當即寫入主存;
3)若是是寫操做,它會致使其餘CPU中對應的緩存行無效。
功能1至關於禁止指令重排序優化,解決了併發變成中有序性問題。
功能2和3,因爲他處理器的緩存遵照了緩存一致性協議,也會把這個變量的值從主存加載到本身的緩存中。這就保證了一個volatile變量在併發編程中,其值在多個緩存中是可見的,解決了併發變成中可見性問題。
注意:volatile並不能解決原子性問題。
經過以上一波操做,volatile完成了併發編程時解決有序性和可見性問題。
補充內容:
Java虛擬機有3種執行方式,分別是解釋執行、混合模式和編譯執行,默認狀況下處於混合模式中
編譯:字節碼 --- jit提早編譯 -- 彙編
解釋:字節碼 – 一段段編譯 – 彙編
混合 :– 運行的過程當中,JIT編譯器生效,針對熱點代碼進行優化
內存屏障參考資料:
https://blog.csdn.net/dd864140130/article/details/56494925
參考資料:
http://www.uucode.net/201504/jvm5
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html