java語言提供了一種稍弱的同步機制,即volatile變數,用來確保將變數的更新操作通知到其他的執行緒。

當把變數宣告為volatile型別後,編譯器與執行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作一起重排序,volatile變數 不會被快取在暫存器或者對處理器不可見的地方,因此在讀取volatile變數時總會返回最新寫入的值。訪問volatile變數不會執行加鎖操作,因此也就不會使得執行執行緒阻塞,因此volatile變數是一種比sychronized關鍵字更加輕量級的同步機制。

一種volatile變數典型的用法:檢查某個狀態標記以判斷是否退出迴圈

volatile boolean asleep;

while( ! asleep)
       countSomeSheep();

volatile變數通常用著某個操作完成、發生中斷或者狀態的標識。儘管volatile變數可以用於表示其他的狀態資訊,但是在使用時要非常小心。例如,volatile的語義不足以保證遞增操作(count++)的原子性,除非你能確保只有一個執行緒對變數進行寫操作。

加鎖機制既可以保證可見性又可以保證原子性,而volatile變數只能保證可見性。

使用volatile應滿足的一些條件

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

如和理解上面的三條,首先看第一條

對變數的寫入操作不依賴變數的當前值,或者你能確保只有單個執行緒更新變數的值

常見的操作如:i++操作,i的寫入需要依賴i自身的值,當有多個執行緒同時執行i++時,A,B讀了i的值,然後進行++操作,實際上得到的值可能只++了一次

如下程式碼:

public class Volatiletest extends Thread {

        static volatile int a=0;


      public void run()
      {

         for  ( int   i  =   0 ; i  <   10 ; i ++ ) 
              try  
         { 
                 a  =  a  +   1 ; 
                 sleep( 300 );    

             } 
              catch  (Exception e) 
             { 
             } 
      }

      public static void main(String []args) throws InterruptedException
      {
         Thread thread[]= new Thread[100];

         for(int i=0;i<100;i++)
                thread[i]= new Volatiletest();

         for(int i=0;i<100;i++)
                thread[i].start();

         for(int i=0;i<100;i++)
                thread[i].join();

         System. out.println("a:" +a);

      }
}

執行,可以得知a的結果並不一定等於1000,很多時候要小於1000


第二條:該變數不會與其他狀態變數一起納入不變性條件中

看一個例子:有範圍值 lower總是小於等於upper 這是一個不變式

  public class NumberRange{
           private volatile int lower ,upper ;
           public int getLower(){
               return lower ;
           }
           public int getUpper(){
               return upper ;
           }
           public void setLower( int value){
               if (value > upper) throw new IllegalArgumentException( ...);
               lower = value;
           }
           public void setUpper( int value){
               if (value < lower) throw new IllegalArgumentException( ...);
               upper = value;
           }
       }

這種方式限制了範圍的狀態變數,因此將 lower 和 upper 欄位定義為 volatile 型別不能夠充分實現類的執行緒安全;從而仍然需要使用同步。否則,如果湊巧兩個執行緒在同一時間使用不一致的值執行 setLower 和 setUpper 的話,則會使範圍處於不一致的狀態。例如,如果初始狀態是 (0,5),同一時間內,執行緒 A 呼叫 setLower⑷ 由於用的是volatile變數,那麼讀取的upper為5,並且執行緒 B 呼叫 setUpper⑶同理,由於用的是volatile變數,讀取的lower為0,顯然這兩個操作交叉存入的值是不符合條件的,那麼兩個執行緒都會通過用於保護不變式的檢查,使得最後的範圍值是 (4,3) —— 一個無效值。至於針對範圍的其他操作,我們需要使 setLower() 和 setUpper() 操作原子化 —— 而將欄位定義為 volatile 型別是無法實現這一目的的。


第三條:由前面可以知道—加鎖機制既可以保證可見性又可以保證原子性,而volatile變數只能保證可見性。

所以volatile變數並不能保證加鎖操作的原子性