volatile 的理解

文章目錄

  • 前言
  • 通過一系列的問題,瞭解volatile
  • 總結

前言

在面試的時候,經常會遇到多線程的問題,然後面試官會提到,你是怎麼做到線程安全的,
然後就會涉及到關鍵字volatile ,這個是在面試過程中高頻率出現的。

所以還是有必要對它進行深入的瞭解下。

通過一系列的問題,瞭解volatile

1 volatile 是怎麼保證線程安全的?

它能夠保證每個線程拿到共享的變量值都是最新的。

1.1 那麼怎麼保證每個線程拿到的都是最新的呢?

這裏需要知道這麼幾個概念,內存的可見性,原子性,有序性。

2 什麼是內存可見性?

java 虛擬機試圖定義一種java 內存模型(JMM),來屏蔽各種硬件和系統內存的訪問差異。
cpu執行指令的速度很快,但是內存訪問的速度就慢很多。所以就出現了
高速緩存。
所有變量都存在主存中(寄存器或者高速緩存),線程有自己的工作內存,
等工作前後都要把值同步到主存中。

那麼工作線程是怎麼操作內存的呢?
先從主存中獲取變量值,再把值load 到工作內存的副本中,然後再傳遞給處理器,
執行完呢,再給工作內存中的副本賦值,然後工作內存再把值傳會給主存,
主存中的值才更新。

這個時候內存的可見性就出來:就是說,一個線程改變了共享變量的值呢,其他線程也被通知到,這就是內存的可見性。

2.1 那麼怎麼來保證可見性呢?

1、java 利用關鍵字volatile來提供可見性。當變量被volatile修飾,
對它的修改就會立即刷回到主存中,這樣其他線程就能獲取到最新值。
2、通過synchronized 和 Lock 也可以保證可見性,在線程釋放鎖之前,把變量刷回主存中。
但是呢,synchronized 和 Lock 的開銷會更大,不建議。

3 什麼是原子性呢?
i = 1;  //只是讀取的操作,所以滿足原子性
j = i;  //讀取i的值1,還要進行賦值操作,所以不是原子性操作。
i = i + 1; //讀取i的值1,還要進行加1,然後再寫回主存中,所以不是原子性操作。

怎麼才能把i++的操作,變成原子性操作呢?
必須藉助synchronized 和 lock 來保證整塊的原子操作,
也就是線程在釋放鎖之前,必須把i的值刷回到主存中。

所以原子性的意思就是:一個操作或者多個操作,要嘛全部執行,要嘛都不執行,執行的話,中途就不能斷掉。

4 什麼是有序性?

JMM 允許編譯器和處理器對指令重排序,就是說不管怎麼重排序,程序執行結果不能改變。

int a = 1; //A
int b = 2; //B
int c = a + b + 1; //C

這樣的語句可以按照 A->B->C執行,也可以B->A->C, 因爲A和B是獨立的語句,而C卻是需要依賴AB的,
所以A B 是可以重排序的,但是C 卻不可以放到 A B 的前面,放前面,就不知道ab, 拿不到a,b 的值了。
JMM 保證了重排序不影響單線程的執行,但是多線程就不能保證了。
比如:

int a = 0;
boolean flag = false;
public void init(){
   a = 1;       //1
   flag = true;   //2
}
public void add(){
     if(flag){       //3
        int result = a + a;  //4
     }
}
4.1 如果有兩個線程同時執行,線程1先執行 init(), 然後線程2 執行add(), 這個時候result 的結果一定是2麼?

結果不一定。爲什麼呢?
有可能得執行過程是這樣的,就是 init() 先執行了 flag = true,線程了立馬接上去執行了 add(),
這個時候其實 a = 1,是沒執行的,也就是 a 還是0,等到執行完 add(),線程1,這個時候才執行賦值操作 a = 1;
那麼顯然就出現問題了,結果就result 的值是0。因爲跳過 a = 1,的操作。
執行的過程變成了 2 -> 3 -> 4 -> 1; 結果肯定不對。

4.2 怎麼解決重排序的問題呢?

給flag加上volatile 這個關鍵字,加上之後呢,就能夠禁止重排序。

5 volatile 是怎麼滿足併發的三大特性(可見性,原子性,有序性)?

比如

int a = 0;
boolean flag = false;
public void init(){
   a = 1;       //1
   flag = true;   //2
}
public void add(){
     if(flag){       //3
        int result = a + a;  //4
     }
}

在多線程中,假如1、2沒有重新排序,3 也不一定能能順利執行的,假設還是線程1先執行init();
線程2再執行add()操作,由於線程1在工作內存中把flag 賦值爲true,不一定立刻寫回主存中,
這個時候線程了執行add()時候,發現flag 還是false,這個時候裏頭的代碼就不會執行。

5.1 所以解決的方案是什麼呢?(怎麼解決flag 的原子性)
int a = 0;
volatile boolean flag = false;  //加上volatile
public void init(){
   a = 1;       //1
   flag = true;   //2
}
public void add(){
     if(flag){       //3
        int result = a + a;  //4
     }
}

加上關鍵字volatile ,這個時候,線程1執行init(),線程2再執行add(),
volatile 限制了指令的重排序,所以 1會在2之前執行。
整過過程的原理是什麼呢?
在寫一個volatile 變量的時候,JMM 會把該線程的本地內存共享變量同步到主存中。
在讀一個volatile 變量的時候呢,JMM 會把該內存的本地內存設置爲無效,線程跑去主存中獲取最新值。

6 volatile 能夠保證可見性,有序性,能夠保證原子性呢?

volatile 只能保證當個變量的讀寫具備原子性,對於volatile++的複合操作是不具備原子性的。
比如 volatile int i = 0; i++;

當多個線程對 i++ 進行操作,就不能保證i 的原子性操作。
這是一個複合的操作:獲取i -> i自增加 -> 回寫i

線程1 和線程2 同時自增i。由於volatile 可見性,所以兩個線程拿到的都是最新的i。
接下來是自增就有問題了,
有可能出現的場景是 線程1 自增i並回寫了,但是線程2 在線程1回寫前 已經拿到i了,
這個時候線程2 就沒拿到線程1的回寫,然後進行了一次自增並回寫。相當於作了重複的工作。
這樣操作的結果就會不對。

6.1 有的人可能會說,線程1對i進行自增的操作,不是會通知其他緩存,並進行重新load麼?

拿的前提是讀,問題是線程1對i進行了自增,線程2 已經提前拿到i了,並不存在再次讀取的情況,也就不會觸發load。
說白了,就是線程1 對i自增操作是一個複合的操作,不是原子操作,導致的。
這裏如果說改成賦值i=2,這種就是原子操作,就不會有問題。

7 volatile 底層的實現機制是什麼呢?

當我們把加了volatile 和 沒有加入volatile 的代碼生成彙編代碼的時候,
會發現volatile 會多加一個lock 前綴指令。

7.1 那麼這個lock前綴指令的功能是幹什麼的呢?

1、會強制寫入主存
2、避免前後指令的CPU重排序
3、讓其他核中的緩存失效(也就是其他線程的緩存失效)

7.2 CPU 和線程的關係是什麼呢?

線程是CPU調度的最小單位
CPU 是多核的,一般一個核對應一個線程。但Intel引入了超線程技術之後,核心數與線程數變成1:2關係

8 一般在哪些地方會用到volatile 呢?

在寫當單例的時候最容易碰到volatile的。
目的就是爲了當多線程同時訪問一個實例,避免出現線程安全問題。
比如:

class SingleInstance {
  private static volatile SingleInstance singleInstance;//這裏使用volatile 關鍵字
  private SingleInstance(){}
  public static SingleInstance getInstance(){
    if (singleInstance == null){
      synchronized (SingleInstance.class){
        if (singleInstance == null){
           singleInstance = new SingleInstance();
        }
      }
    }
    return  singleInstance;
  }
}

總結

volatile 關鍵字主要就是用來確保多個線程訪問同一個共享變量的時候,拿到的都是最新值。

如果對你有一點點幫助,那是值得高興的事情。:)
我的csdn:http://blog.csdn.net/shenshizhong
我的簡書:http://www.jianshu.com/u/345daf0211ad

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