Java volatile 用法搀擂、原理

用在多線程,同步變量卷玉。 線程為了提高效率哨颂,將某成員變量(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B相种。只在某些動作時才進行A和B的同步咆蒿。因此存在A和B不一致的情況。volatile就是用來避免這種情況的蚂子。volatile告訴jvm, 它所修飾的變量不保留拷貝缭黔,直接訪問主內(nèi)存中的(也就是上面說的A)

在Java內(nèi)存模型中食茎,有main memory,每個線程也有自己的memory (例如寄存器)馏谨。為了性能别渔,一個線程會在自己的memory中保持要訪問的變量的副本。這樣就會出現(xiàn)同一個變量在某個瞬間惧互,在一個線程的memory中的值可能與另外一個線程memory中的值哎媚,或者main memory中的值不一致的情況。

一個變量聲明為volatile喊儡,就意味著這個變量是隨時會被其他線程修改的拨与,因此不能將它cache在線程memory中。以下例子展現(xiàn)了volatile的作用:

public class StoppableTask extends Thread {  
  
  private volatile boolean pleaseStop;  
  
  
  public void run() {  
  
    while (!pleaseStop) {  
  
     // do some stuff...  
  
    }  
  
 }  
  
  
  public void tellMeToStop() {  
  
   pleaseStop = true;  
  
  }  
  
}  

假如pleaseStop沒有被聲明為volatile艾猜,線程執(zhí)行run的時候檢查的是自己的副本买喧,就不能及時得知其他線程已經(jīng)調(diào)用tellMeToStop()修改了pleaseStop的值捻悯。

Volatile一般情況下不能代替sychronized,因為volatile不能保證操作的原子性淤毛,即使只是i++今缚,實際上也是由多個原子操作組成:read i; inc; write i,假如多個線程同時執(zhí)行i++低淡,volatile只能保證他們操作的i是同一塊內(nèi)存姓言,但依然可能出現(xiàn)寫入臟數(shù)據(jù)的情況。如果配合Java 5增加的atomic wrapper classes蔗蹋,對它們的increase之類的操作就不需要sychronized何荚。

Reference:
http://www.javamex.com/tutorials/synchronization_volatile.shtml
http://www.javamex.com/tutorials/synchronization_volatile_java_5.shtml
http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
恐怕比較一下volatile和synchronized的不同是最容易解釋清楚的。volatile是變量修飾符纸颜,而synchronized則作用于一段代碼或方法兽泣;看如下三句get代碼:

int i1;               
int geti1() {return i1;}   
volatile int i2;   
int geti2()  
{return i2;}   
int i3;                
synchronized int geti3() {return i3;}   
  geti1()  

得到存儲在當(dāng)前線程中i1的數(shù)值。多個線程有多個i1變量拷貝胁孙,而且這些i1之間可以互不相同唠倦。換句話說,另一個線程可能已經(jīng)改變了它線程內(nèi)的i1值涮较,而這個值可以和當(dāng)前線程中的i1值不相同稠鼻。事實上,Java有個思想叫“主”內(nèi)存區(qū)域狂票,這里存放了變量目前的“準(zhǔn)確值”候齿。每個線程可以有它自己的變量拷貝,而這個變量拷貝值可以和“主”內(nèi)存區(qū)域里存放的不同闺属。因此實際上存在一種可能:“主”內(nèi)存區(qū)域里的i1值是1慌盯,線程1里的i1值是2,線程2里的i1值是3——這在線程1和線程2都改變了它們各自的i1值掂器,而且這個改變還沒來得及傳遞給“主”內(nèi)存區(qū)域或其他線程時就會發(fā)生亚皂。
  而 geti2()得到的是“主”內(nèi)存區(qū)域的i2數(shù)值。用volatile修飾后的變量不允許有不同于“主”內(nèi)存區(qū)域的變量拷貝国瓮。換句話說灭必,一個變量經(jīng) volatile修飾后在所有線程中必須是同步的;任何線程中改變了它的值乃摹,所有其他線程立即獲取到了相同的值禁漓。理所當(dāng)然的,volatile修飾的變量存取時比一般變量消耗的資源要多一點孵睬,因為線程有它自己的變量拷貝更為高效播歼。
  既然volatile關(guān)鍵字已經(jīng)實現(xiàn)了線程間數(shù)據(jù)同步,又要 synchronized干什么呢掰读?呵呵荚恶,它們之間有兩點不同撩穿。首先,synchronized獲得并釋放監(jiān)視器——如果兩個線程使用了同一個對象鎖谒撼,監(jiān)視器能強制保證代碼塊同時只被一個線程所執(zhí)行——這是眾所周知的事實食寡。但是,synchronized也同步內(nèi)存:事實上廓潜,synchronized在“ 主”內(nèi)存區(qū)域同步整個線程的內(nèi)存抵皱。因此,執(zhí)行g(shù)eti3()方法做了如下幾步:

  1. 線程請求獲得監(jiān)視this對象的對象鎖(假設(shè)未被鎖辩蛋,否則線程等待直到鎖釋放)
  2. 線程內(nèi)存的數(shù)據(jù)被消除呻畸,從“主”內(nèi)存區(qū)域中讀入(Java虛擬機能優(yōu)化此步。悼院。伤为。[后面的不知道怎么表達(dá),汗])
  3. 代碼塊被執(zhí)行
  4. 對于變量的任何改變現(xiàn)在可以安全地寫到“主”內(nèi)存區(qū)域中(不過geti3()方法不會改變變量值)
  5. 線程釋放監(jiān)視this對象的對象鎖
      因此volatile只是在線程內(nèi)存和“主”內(nèi)存間同步某個變量的值,而synchronized通過鎖定和解鎖某個監(jiān)視器同步所有變量的值据途。顯然synchronized要比volatile消耗更多資源绞愚。
    volatile關(guān)鍵字相信了解Java多線程的讀者都很清楚它的作用。volatile關(guān)鍵字用于聲明簡單類型變量颖医,如int位衩、float、 boolean等數(shù)據(jù)類型熔萧。如果這些簡單數(shù)據(jù)類型聲明為volatile糖驴,對它們的操作就會變成原子級別的。但這有一定的限制佛致。例如贮缕,下面的例子中的n就不是原子級別的:
package  mythread;  
  
public   class  JoinThread  extends  Thread  
{  
     public   static volatile int  n  =   0 ;  
    public   void  run()  
    {  
         for  ( int  i  =   0 ; i  <   10 ; i ++ )  
             try   
        {  
                n  =  n  +   1 ;  
                sleep( 3 );  //  為了使運行結(jié)果更隨機,延遲3毫秒   
  
            }  
             catch  (Exception e)  
            {  
            }  
    }  
  
     public   static   void  main(String[] args)  throws  Exception  
    {  
  
        Thread threads[]  =   new  Thread[ 100 ];  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  建立100個線程   
            threads[i]  =   new  JoinThread();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  運行剛才建立的100個線程   
            threads[i].start();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  100個線程都執(zhí)行完后繼續(xù)   
            threads[i].join();  
        System.out.println( " n= "   +  JoinThread.n);  
    }  
}   

如果對n的操作是原子級別的俺榆,最后輸出的結(jié)果應(yīng)該為n=1000跷睦,而在執(zhí)行上面積代碼時,很多時侯輸出的n都小于1000肋演,這說明n=n+1不是原子級別的操作。原因是聲明為volatile的簡單變量如果當(dāng)前值由該變量以前的值相關(guān)烂琴,那么volatile關(guān)鍵字不起作用爹殊,也就是說如下的表達(dá)式都不是原子操作:

n  =  n  +   1 ; 
n ++ ; 

如果要想使這種情況變成原子操作,需要使用synchronized關(guān)鍵字奸绷,如上的代碼可以改成如下的形式:

package  mythread;  
  
public   class  JoinThread  extends  Thread  
{  
     public   static int  n  =   0 ;  
  
     public static   synchronized   void  inc()  
    {  
        n ++ ;  
    }  
     public   void  run()  
    {  
         for  ( int  i  =   0 ; i  <   10 ; i ++ )  
             try   
            {  
                inc();  //  n = n + 1 改成了 inc();   
                sleep( 3 );  //  為了使運行結(jié)果更隨機梗夸,延遲3毫秒   
  
            }  
             catch  (Exception e)  
            {  
            }  
    }  
  
     public   static   void  main(String[] args)  throws  Exception  
    {  
  
        Thread threads[]  =   new  Thread[ 100 ];  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  建立100個線程   
            threads[i]  =   new  JoinThread();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  運行剛才建立的100個線程   
            threads[i].start();  
         for  ( int  i  =   0 ; i  <  threads.length; i ++ )  
             //  100個線程都執(zhí)行完后繼續(xù)   
            threads[i].join();  
        System.out.println( " n= "   +  JoinThread.n);  
    }  
}   

上面的代碼將n=n+1改成了inc(),其中inc方法使用了synchronized關(guān)鍵字進行方法同步号醉。因此反症,在使用volatile關(guān)鍵字時要慎重辛块,并不是只要簡單類型變量使用volatile修飾,對這個變量的所有操作都是原來操作铅碍,當(dāng)變量的值由自身的上一個決定時润绵,如n=n+1、n++ 等胞谈,volatile關(guān)鍵字將失效尘盼,只有當(dāng)變量的值和自身上一個值無關(guān)時對該變量的操作才是原子級別的,如n = m + 1烦绳,這個就是原級別的卿捎。所以在使用volatile關(guān)鍵時一定要謹(jǐn)慎,如果自己沒有把握径密,可以使用synchronized來代替volatile午阵。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市享扔,隨后出現(xiàn)的幾起案子底桂,更是在濱河造成了極大的恐慌,老刑警劉巖伪很,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件戚啥,死亡現(xiàn)場離奇詭異,居然都是意外死亡锉试,警方通過查閱死者的電腦和手機猫十,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呆盖,“玉大人拖云,你說我怎么就攤上這事∮τ郑” “怎么了宙项?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長株扛。 經(jīng)常有香客問我尤筐,道長,這世上最難降的妖魔是什么洞就? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任盆繁,我火速辦了婚禮,結(jié)果婚禮上旬蟋,老公的妹妹穿的比我還像新娘油昂。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布冕碟。 她就那樣靜靜地躺著拦惋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪安寺。 梳的紋絲不亂的頭發(fā)上厕妖,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音我衬,去河邊找鬼叹放。 笑死,一個胖子當(dāng)著我的面吹牛挠羔,可吹牛的內(nèi)容都是我干的井仰。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼破加,長吁一口氣:“原來是場噩夢啊……” “哼俱恶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起范舀,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤合是,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锭环,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聪全,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年辅辩,在試婚紗的時候發(fā)現(xiàn)自己被綠了难礼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楚午。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡妄迁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姑躲,到底是詐尸還是另有隱情撩鹿,我是刑警寧澤谦炬,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站节沦,受9級特大地震影響键思,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜甫贯,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一吼鳞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧获搏,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至裸卫,卻和暖如春仿贬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背墓贿。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工茧泪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人聋袋。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓队伟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親幽勒。 傳聞我的和親對象是個殘疾皇子嗜侮,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容