Java多線程基礎:Volatile關鍵字


Java多線程基礎:Volatile關鍵字

Volatile關鍵字

  Volatile關鍵字主要是使變量在多個線程間可見

線程的私有堆棧

  Java內存模型告訴我們,各個線程會將共享變量從主內存中拷貝到工作內存,然后執行引擎會基於工作內存中的數據進行操作處理 。 

  

 這個時機對普通變量是沒有規定的,而針對volatile修飾的變量給java虛擬機特殊的約定,線程對volatile變量的修改會立刻被其他線程所感知,即不會出現數據臟讀的現象,從而保證數據的“可見性”

  

 實現原理

  在生成匯編代碼時會在volatile修飾的共享變量進行寫操作的時候會多出Lock前綴的指令。主要有這兩個方面的影響:

  1. 將當前處理器緩存行的數據寫回系統內存,即主內存
  2. 這個寫回內存的操作會使得其他CPU里緩存了該內存地址的數據無效。

  為了提高處理速度,處理器不直接和內存進行通信,而是先將系統內存的數據讀到內部緩存(L1,L2或其他)后再進行操作,但操作完不知道何時會寫到內存。如果對聲明了volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存。但是,就算寫回到內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題。所以,在多處理器下,為了保證各個處理器的緩存是一致的,就會實現緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己緩存的值是不是過期了,當處理器發現自己緩存行對應的內存地址被修改,就會將當前處理器的緩存行設置成無效狀態,當處理器對這個數據進行修改操作的時候,會重新從系統內存中把數據讀到處理器緩存里。因此,經過分析我們可以得出如下結論:

  1. Lock前綴的指令會引起處理器緩存寫回內存;
  2. 一個處理器的緩存回寫到內存會導致其他處理器的緩存失效;
  3. 當處理器發現本地緩存失效后,就會從內存中重讀該變量數據,即可以獲取當前最新值。

  這樣針對volatile變量通過這樣的機制就使得每個線程都能獲得該變量的最新值。

 

無法保證原子性

  Volatile關鍵字雖然增加了實例變量在多個線程之間的可見性,但是他卻不具備同步性,也就不具備原子性。

自增操作演示

  看一下下面代碼:

public class VolatileExample {
    private static volatile int counter = 0;

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++)
                        counter++;
                }
            });
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(counter);
    }
}
  開啟10個線程,每個線程都自加10000次,如果不出現線程安全的問題最終的結果應該就是:10*10000 = 100000;可是運行多次都是小於100000的結果,問題在於 volatile並不能保證原子性,在前面說過counter++這並不是一個原子操作。
說明:自增操作分解為下面三步
1.讀取變量counter的值;
2.對counter加一;
3.將新值賦值給變量counter。
  如果線程A讀取counter到工作內存后,其他線程對這個值已經做了自增操作后,那么線程A的這個值自然而然就是一個過期的值,因此,總結果必然會是小於100000的。原因在於volatile保證修改立即由當前線程工作內存同步到主內存,但其他線程仍需要從主內存取才能保證線程同步
 

Volatile使用場景

  加鎖機制既可以確保可見性又可以確保原子性,而volatile變量只可以確保可見性

  當且僅當滿足以下所有條件的時,才應該使用volatile變量:

  • 對變量的寫入操作不依賴變量的當前值,或者你能確保只有單個線程更新變量的值
  • 該變量不會與其他狀態變量一起納入不變性條件中
  • 在訪問變量時不需要加鎖

狀態標記

volatile boolean inited = false;
//線程1:
context = loadContext();  
inited = true;            
 
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

  

參考資料

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM