Java內存模型中volatile關鍵字的做用

volatile做用總結html

 1. 強制線程從公共內存中取得變量的值,而不是從線程的私有的本地內存中,volatile修飾的變量不具備原子性(修改一個變量的值不能同步)。緩存

 2. 保證volatile修飾的變量在被一個線程修改後,會被強制當即刷新到主存(可見性),其餘線程若是有該變量的緩存行,會被設置爲無效。app

 3. 禁止指令重排(有序性)性能

   a.happen-beforehttp://www.javashuo.com/article/p-ttuhluqc-cv.html; 優化

   b.編譯器在生成字節碼時, 會在指令序列中插入內存屏障,會多出一個 lock 前綴指令spa

 內存屏障是一組處理器指令,解決禁止指令重排序內存可見性的問題,保證指令重排序後與以前的輸出結果一 樣,使性能獲得優化。處理器在進行重排序時是會考慮指令之間 的數據依賴性。線程

  

 指令重排:Java 語言規範規定了JVM線程內部維持順序化語義,也就是說只要程序的最終結果等同於它在嚴格的順序化環境下的結果,那麼指令的執行順序就可能與代碼的順序不一致。這個過程經過叫作指令的重排序。htm

 指令重排序存在的意義在於:JVM可以根據處理器的特性CPU多級緩存系統多核處理器等)適當的新排序機器指令,使機器指令更符合CPU的執行特色,最大限度的發揮機器的性能,提升效率blog

 雙重校驗鎖 DCL(double checked locking)--使用 volatile 的場景之一。排序

 

 一旦一個共享變量(類的成員變量、類的靜態成員變量)被 volatile 修飾 以後,那麼就具有了兩層語義:

  1)保證了不一樣線程對這個變量進行讀取時的可見性,即一個線程修改 了某個變量的值,這新值對其餘線程來講是當即可見的。(volatile 解決了 線程間共享變量的可見性問題)。

   第一:使用 volatile 關鍵字會強制修改的值當即寫入主存

   第二:使用 volatile 關鍵字的話,當線程 2 進行修改時,會致使線程 1 的 工做內存中緩存變量 stop 的緩存行無效(反映到硬件層的話,就是 CPU L1 或者 L2 緩存中對應的緩存行無效);

   第三:因爲線程 1 的工做內存中緩存變量 stop 的緩存行無效,因此線程 1 再次讀取變量 stop 的值時會去主存讀取。 那麼,在線程 2 修改 stop 值時(固然這裏包括 2 個操做,修改線程 2 工 做內存中的值,而後將修改後的值寫入內存),會使得線程 1 的工做內存中緩 存變量 stop 的緩存行無效,而後線程 1 讀取時,發現本身的緩存行無效,它會 等待緩存行對應的主存地址被更新以後,而後去對應的主存讀取最新的值。 那麼線程 1 讀取到的就是最新的正確的值。 

  2)禁止進行指令重排序,阻止編譯器對代碼的優化。

   volatile 關鍵字禁止指令重排序有兩層意思: I)當程序執行到 volatile 變量的讀操做或者寫操做時,在其前面的操做的 更改確定所有已經進行,且結果已經對後面的操做可見;在其後面的操做確定 尚未進行; II)在進行指令優化時,不能 volatile 變量前面的語句放在其後面執行, 也不能把 volatile 變量後面的語句放到其前面執行。 爲了實現 volatile 的內存語義,加入 volatile 關鍵字時,編譯器在生成字節碼時, 會在指令序列中插入內存屏障,會多出一個 lock 前綴指令。

  內存屏障,有 2 個做用:1.於這個內存屏障的指令必須先執行,於這個內存屏障 的指令必須後執行。2.使得內存可見性。因此,若是你的字段是 volatile,在讀指令前插入讀屏障,可讓高速緩存中的數據失效,從新從主內存加載數據。在寫指令以後插入寫屏障,能讓寫入緩存的最新數據寫回到主內存。

  lock 前綴指令在多核處理器下會引起了兩件事情: 1.將當前處理器中這個變量所在緩存行的數據會寫回到系統內存。這個寫回內存的 操做會引發在其餘 CPU 裏緩存了該內存地址的數據無效。可是就算寫回到內存,若是 其餘處理器緩存的值仍是舊的,再執行計算操做就會有問題,因此在多處理器下,爲了 保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每一個處理器經過嗅探在總 線上傳播的數據來檢查本身緩存的值是否是過時了,當處理器發現本身緩存行對應的內 存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器要對這個數據進 行修改操做的時候,會強制從新從系統內存裏把數據讀處處理器緩存裏。 2.它確保指令重排序時不會把其後面的指令排到內存屏障以前的位置,也不會把前面 的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操做已經全 部完成。

 

  內存屏障能夠被分爲如下幾種類型:

  LoadLoad 屏障:對於這樣的語句 Load1; LoadLoad; Load2,在 Load2 及後續讀取 操做要讀取的數據被訪問前,保證 Load1 要讀取的數據被讀取完畢;

  StoreStore 屏障:對於這樣的語句 Store1; StoreStore; Store2,在 Store2 及後續寫 入操做執行前,保證 Store1 的寫入操做對其它處理器可見;

  LoadStore 屏障:對於這樣的語句 Load1; LoadStore; Store2,在 Store2 及後續寫 入操做被刷出前,保證 Load1 要讀取的數據被讀取完畢;

  StoreLoad 屏障:對於這樣的語句 Store1; StoreLoad; Load2,在 Load2 及後續所 有讀取操做執行前,保證 Store1 的寫入全部處理器可見;它的開銷是四種屏障中最 大的。在大多數處理器的實現中,這個屏障是個萬能屏障,兼具其它三種內存屏障的功 能。

相關文章
相關標籤/搜索