Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比,volatile 變量所需的編碼較少,并且運(yùn)行時開銷也較少,但是它所能實(shí)現(xiàn)的功能也僅是 synchronized 的一部分。本文介紹了幾種有效使用 volatile 變量的模式,并強(qiáng)調(diào)了幾種不適合使用 volatile 變量的情形。
鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility);コ饧匆淮沃辉试S一個線程持有某個特定的鎖,因此可使用該特性實(shí)現(xiàn)對共享數(shù)據(jù)的協(xié)調(diào)訪問協(xié)議,這樣,一次就只有一個線程能夠使用該共享數(shù)據(jù)。可見性要更加復(fù)雜一些,它必須確保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的 —— 如果沒有同步機(jī)制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這將引發(fā)許多嚴(yán)重問題。
Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發(fā)現(xiàn) volatile 變量的最新值。Volatile 變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當(dāng)前值與修改后值之間沒有約束。因此,單獨(dú)使用 volatile 還不足以實(shí)現(xiàn)計數(shù)器、互斥鎖或任何具有與多個變量相關(guān)的不變式(Invariants)的類(例如 “start <=end”)。
出于簡易性或可伸縮性的考慮,您可能傾向于使用 volatile 變量而不是鎖。當(dāng)使用 volatile 變量而非鎖時,某些習(xí)慣用法(idiom)更加易于編碼和閱讀。此外,volatile 變量不會像鎖那樣造成線程阻塞,因此也很少造成可伸縮性問題。在某些情況下,如果讀操作遠(yuǎn)遠(yuǎn)大于寫操作,volatile 變量還可以提供優(yōu)于鎖的性能優(yōu)勢。
您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
對變量的寫操作不依賴于當(dāng)前值。該變量沒有包含在具有其他變量的不變式中。實(shí)際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨(dú)立于任何程序的狀態(tài),包括變量的當(dāng)前狀態(tài)。
第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器。雖然增量操作(x++)看上去類似一個單獨(dú)操作,實(shí)際上它是一個由讀取-修改-寫入操作序列組成的組合操作,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性。實(shí)現(xiàn)正確的操作需要使 x 的值在操作期間保持不變,而 volatile 變量無法實(shí)現(xiàn)這點(diǎn)。(然而,如果將值調(diào)整為只從單個線程寫入,那么可以忽略第一個條件。)
大多數(shù)編程情形都會與這兩個條件的其中之一沖突,使得 volatile 變量不能像 synchronized 那樣普遍適用于實(shí)現(xiàn)線程安全。清單 1 顯示了一個非線程安全的數(shù)值范圍類。它包含了一個不變式 —— 下界總是小于或等于上界。
清單 1. 非線程安全的數(shù)值范圍類
@NotThreadSafe
public class NumberRange {
private 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;
}
}
|
這種方式限制了范圍的狀態(tài)變量,因此將 lower 和 upper 字段定義為 volatile 類型不能夠充分實(shí)現(xiàn)類的線程安全;從而仍然需要使用同步。否則,如果湊巧兩個線程在同一時間使用不一致的值執(zhí)行 setLower 和 setUpper 的話,則會使范圍處于不一致的狀態(tài)。例如,如果初始狀態(tài)是(0, 5),同一時間內(nèi),線程 A 調(diào)用 setLower(4) 并且線程 B 調(diào)用 setUpper(3),顯然這兩個操作交叉存入的值是不符合條件的,那么兩個線程都會通過用于保護(hù)不變式的檢查,使得最后的范圍值是 (4, 3) —— 一個無效值。至于針對范圍的其他操作,我們需要使 setLower() 和setUpper() 操作原子化 —— 而將字段定義為 volatile 類型是無法實(shí)現(xiàn)這一目的的。
使用 volatile 變量的主要原因是其簡易性:在某些情形下,使用 volatile 變量要比使用相應(yīng)的鎖簡單得多。使用 volatile 變量次要原因是其性能:某些情況下,volatile 變量同步機(jī)制的性能要優(yōu)于鎖。
很難做出準(zhǔn)確、全面的評價,例如 “X 總是比 Y 快”,尤其是對 JVM 內(nèi)在的操作而言。(例如,某些情況下 VM 也許能夠完全刪除鎖機(jī)制,這使得我們難以抽象地比較 volatile 和 synchronized 的開銷。)就是說,在目前大多數(shù)的處理器架構(gòu)上,volatile 讀操作開銷非常低 —— 幾乎和非 volatile 讀操作一樣。而 volatile 寫操作的開銷要比非 volatile 寫操作多很多,因為要保證可見性需要實(shí)現(xiàn)內(nèi)存界定(Memory Fence),即便如此,volatile 的總開銷仍然要比鎖獲取低。
2015職稱計算機(jī)考試書PowerPoint2007中 .. 定價:¥45 優(yōu)惠價:¥42 更多書籍 | |
2015年全國職稱計算機(jī)考試教材(2007模 .. 定價:¥225 優(yōu)惠價:¥213 更多書籍 |