歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java並發編程 -volatile關鍵字

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變量並不能保證加鎖操作的原子性

Copyright © Linux教程網 All Rights Reserved