更多精彩文章,請關注xhJaver,京東工程師和你一塊兒成長
通常用來修飾共享變量,保證可見性和能夠禁止指令重排java
(可是對單次讀或者寫保證原子性)mysql
如下代碼建議使用PC端來查看,複製黏貼直接運行,都有詳細註釋
咱們來寫個代碼測試一下,多線程修改共享變量時究竟需不須要用volatile修飾變量面試
public class Task implements Runnable{ @Override public void run() { System.out.println("這是"+Thread.currentThread().getName()+"線程開始,flag是 "+Demo.flag); //當共享變量是true時,就一直卡在這裏,不輸出下面那句話 // 當flag是false時,輸出下面這句話 while (Demo.flag){ } System.out.println("這是"+Thread.currentThread().getName()+"線程結束,flag是 "+Demo.flag); } }
2.其次,咱們建立個測試類sql
class Demo { //共享變量,還沒用volatile修飾 public static boolean flag = true ; public static void main(String[] args) throws InterruptedException { System.out.println("這是"+Thread.currentThread().getName()+"線程開始,flag是 "+flag); //開啓剛纔線程 new Thread(new Task()).start(); try { //沉睡一秒,確保剛纔的線程已經跑到了while循環 //要否則還沒跑到while循環,主線程就將flag變爲false Thread.sleep(1000L); } catch (InterruptedException e) { e.printStackTrace(); } //改變共享變量flag轉爲false flag = false; System.out.println("這是"+Thread.currentThread().getName()+"線程結束,flag是 "+flag); } }
3.咱們查看一下輸出結果緩存
可見,程序並無結束,他卡在了這裏,爲何卡在了這裏呢,就是由於咱們在主線程修改了共享變量flag爲false,可是另外一個線程沒有感知到,這個變量的修改對另外一個線程不可見多線程
public static volatile boolean flag = true
ide
可見,此次主線程修改的變量被另外一個線程所感知到了,保證了變量的可見性測試
那麼,神奇的 volatile 底層到底作了什麼呢,你的改變,逃不過他的法眼?爲何不用他修飾變量的話,變量的改變其餘線程就看不見?優化
回答此問題的時候首先,咱們須要瞭解一下JMM(Java內存模型)this
注: 本地內存是JMM的一種抽象,並非真實存在的,本地內存它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化以後的一個數據存放位置
由此咱們能夠分析出來,主線程修改了變量,可是其餘線程不知道,有兩種狀況
當咱們用 volatile
關鍵字修飾共享變量時就能夠作到如下兩點
爲了提升程序運行效率,編譯器和cpu會對代碼執行的順序進行重排列,可這有時候會帶來不少問題
咱們來看下代碼
//指令重排測試 public class Demo2 { private Integer number = 10; private boolean flag = false; private Integer result = 0; public void write(){ this.flag = true; // L1 this.number = 20; // L2 } public void reader(){ while (this.flag){ // L3 this.result = this.number + 1; // L4 } } }
假如說咱們有A、B兩個線程 他們分別執行write()方法和 reader()方法,執行的順序有可能以下圖所示
這個時候,咱們就能夠用volatile
關鍵字來解決這個問題,很簡單,只需
private volatile Integer number = 10;
A線程在修改number
變量爲20的時候,就確保這句代碼的前面的代碼必定在此行代碼以前執行,在number
處插入了 內存屏障 ,爲了實現volatile的內存語義,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排
內存屏障又是什麼呢?一共有四種內存屏障類型,他們分別是
LoadLoad屏障:
LoadStore屏障:
StoreLoad屏障:
StoreStore屏障:
> StoreLoad 是一個全能型的屏障,同時具備其餘3個屏障的效果。執行該屏障的花銷比較昂貴,由於處理器一般要把當前的寫緩衝區的內容所有刷新到內存中(Buffer Fully Flush)
那麼volatile和這四種內存屏障又有什麼關係呢,具體是怎麼插入的呢?
volatile寫 (先後都插入屏障)
volatile讀(只在後面插入屏障)
官方提供的表格是這樣的
咱們此時回過頭來在看咱們的那個程序
this.flag = true; // L1 this.number = 20; // L2
因爲number被volatile修飾了,L2這句話是volatile寫,那麼加入屏障後就應該是這個樣子
this.flag = true; // L1 // StoreStore 確保flag數據對其餘處理器可見(刷新到內存)先於number及全部後續存儲指令的存儲 this.number = 20; // L2 // StoreLoad 確保number數據對其餘處理器可見(刷新到內存)先於全部後續存儲指令的裝載
因此L1,L2的執行順序不被重排序
ps:總部四號樓真是愈來愈好了,獎勵本身一杯奶茶
更多精彩,請關注公衆號xhJaver,京東工程師和你一塊兒成長