歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> 理解Java中的volatile關鍵字

理解Java中的volatile關鍵字

日期:2017/3/1 9:40:09   编辑:Linux編程

Java語言包含兩種內在的同步機制:同步塊(或方法)和 volatile 變量。這兩種機制的提出都是為了實現代碼線程的安全性。Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比,volatile 變量所需的編碼較少,並且運行時開銷也較少,但是它所能實現的功能也僅是 synchronized 的一部分。

Java編程思想(第4版) 中文清晰PDF完整版 http://www.linuxidc.com/Linux/2014-08/105403.htm

編寫高質量代碼 改善Java程序的151個建議 PDF高清完整版 http://www.linuxidc.com/Linux/2014-06/103388.htm

Java 8簡明教程 http://www.linuxidc.com/Linux/2014-03/98754.htm

Java對象初始化順序的簡單驗證 http://www.linuxidc.com/Linux/2014-02/96220.htm

Java對象值傳遞和對象傳遞的總結 http://www.linuxidc.com/Linux/2012-12/76692.htm

volatile 寫和讀的內存語義
線程 A 寫一個 volatile 變量,實質上是線程 A 向接下來將要讀這個 volatile 變量的某個線程發出了(其對共享變量所在修改的)消息。

線程 B 讀一個 volatile 變量,實質上是線程 B 接收了之前某個線程發出的(在寫這個 volatile 變量之前對共享變量所做修改的)消息。

線程 A 寫一個 volatile 變量,隨後線程 B 讀這個 volatile 變量,這個過程實質上是線程 A 通過主內存向線程 B 發送消息。

JMM(java內存模型)的支持原理

volatile用來確保將變量的更新操作通知到其它線程。當把變量聲明為volatile類型後,編譯器與運行時都會注意到這個變量時共享的,因此不會將該變量的操作與其它內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其它處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。嚴格限制編譯器和處理器對 volatile 變量與普通變量的重排序,確保 volatile 的寫-讀和鎖的釋放-獲取具有相同的內存語義。從編譯器重排序規則和處理器內存屏障插入策略來看,只要 volatile 變量與普通變量之間的重排序可能會破壞 volatile 的內存語義,這種重排序就會被編譯器重排序規則和處理器內存屏障插入策略禁止。編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。

volatile變量對可見性的影響比volatile變量本身更為重要。當線程A首先寫入一個volatile變量並且線程B隨後讀取該變量時,在寫入volatile變量之前對A可見的所有變量的值,在B讀取了volatile變量後,對B也是可見的。因此,從內存可見性的角度來看,寫入volatile變量相當於退出同步代碼塊,而讀取volatile變量就相當於進入同步代碼塊。我們可以簡單理解為當要讀取一個volatile變量時總是伴隨著先要到主內存刷新取到最新值,而寫變量時會把值直接寫到主內存以對其它線程可見,這些都是由JMM(java內存模型)禁止指令重排序做保證的。不建議過度依賴volatile變量提供的可見性。如果在代碼中依賴volatile變量來控制狀態的可見性,通常比使用鎖的代碼更脆弱,也更難理解。

volatile典型用法

僅當volatile變量能簡化代碼的實現以及對同步策略的驗證時,才應該使用它們。如果在驗證正確性時需要對可見性進行復雜的判斷,那麼就不要使用volatile變量。volatile變量的正確使用方式包括:確保它們自身狀態的可見性,確保他們所引用對象的狀態的可見性,以及標示一些重要的程序聲明周期事件的發生,如初始化或關閉。

volatile boolean asleep;
...
while(!asleep)
countSomeSheep();

上面是volatile變量的一種典型用法:檢查某個狀態標記以判斷是否退出循環。在這個示例中,線程試圖通過類似於數數的傳統方法進入休眠狀態。為了使這個示例能正確執行,asleep必須為volatile變量。否則,當asleep被另一個線程修改時,執行
判斷的線程卻發現不了。我們也可以用鎖來確保asleep更新操作的可見性,但是這樣將使代碼變得更加復雜。
雖然volatile很方便,但也存在一些局限性。volatile變量通常用作某個操作完成發生中斷的標志。盡管volatile變量也可以用於表示其他的狀態信息,但在使用時要非常小心。例如volatile的語義不足以確保遞增操作(count++)的原子性,除非你能確保只有一個線程對變量執行寫操作。

加鎖機制既可以確保可見性又可以確保原子性,而volatile變量只能確保可見性。
當且僅當滿足以下所有條件時,才應該使用volatile變量。

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

參考文獻:

Java並發編程實戰 http://www.linuxidc.com/Linux/2014-09/106561.htm

Copyright © Linux教程網 All Rights Reserved