很多并發(fā)性專家事實上往往引導用戶遠離 volatile 變量,因為使用它們要比使用鎖更加容易出錯。然而,如果謹慎地遵循一些良好定義的模式,就能夠在很多場合內安全地使用 volatile 變量。要始終牢記使用 volatile 的限制 —— 只有在狀態(tài)真正獨立于程序內其他內容時才能使用 volatile —— 這條規(guī)則能夠避免將這些模式擴展到不安全的用例。
也許實現(xiàn) volatile 變量的規(guī)范使用僅僅是使用一個布爾狀態(tài)標志,用于指示發(fā)生了一個重要的一次性事件,例如完成初始化或請求停機。
很多應用程序包含了一種控制結構,形式為 “在還沒有準備好停止程序時再執(zhí)行一些工作”,如清單 2 所示:
清單 2. 將 volatile 變量作為狀態(tài)標志使用
volatile boolean shutdownRequested;
...
public void shutdown() { shutdownRequested = true ; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
|
很可能會從循環(huán)外部調用 shutdown() 方法 —— 即在另一個線程中 —— 因此,需要執(zhí)行某種同步來確保正確實現(xiàn) shutdownRequested 變量的可見性。(可能會從 JMX 偵聽程序、GUI 事件線程中的操作偵聽程序、通過 RMI 、通過一個 Web 服務等調用)。然而,使用 synchronized塊編寫循環(huán)要比使用清單 2 所示的 volatile 狀態(tài)標志編寫麻煩很多。由于 volatile 簡化了編碼,并且狀態(tài)標志并不依賴于程序內任何其他狀態(tài),因此此處非常適合使用 volatile。
這種類型的狀態(tài)標記的一個公共特性是:通常只有一種狀態(tài)轉換;shutdownRequested 標志從 false 轉換為 true,然后程序停止。這種模式可以擴展到來回轉換的狀態(tài)標志,但是只有在轉換周期不被察覺的情況下才能擴展(從 false 到 true,再轉換到 false)。此外,還需要某些原子狀態(tài)轉換機制,例如原子變量。
缺乏同步會導致無法實現(xiàn)可見性,這使得確定何時寫入對象引用而不是原語值變得更加困難。在缺乏同步的情況下,可能會遇到某個對象引用的更新值(由另一個線程寫入)和該對象狀態(tài)的舊值同時存在。(這就是造成著名的雙重檢查鎖定(double-checked-locking)問題的根源,其中對象引用在沒有同步的情況下進行讀操作,產(chǎn)生的問題是您可能會看到一個更新的引用,但是仍然會通過該引用看到不完全構造的對象)。
實現(xiàn)安全發(fā)布對象的一種技術就是將對象引用定義為 volatile 類型。清單 3 展示了一個示例,其中后臺線程在啟動階段從數(shù)據(jù)庫加載一些數(shù)據(jù)。其他代碼在能夠利用這些數(shù)據(jù)時,在使用之前將檢查這些數(shù)據(jù)是否曾經(jīng)發(fā)布過。
清單 3. 將 volatile 變量用于一次性安全發(fā)布
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while ( true ) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null )
doSomething(floobleLoader.theFlooble);
}
}
}
|
如果 theFlooble 引用不是 volatile 類型,doWork() 中的代碼在解除對 theFlooble 的引用時,將會得到一個不完全構造的 Flooble。
該模式的一個必要條件是:被發(fā)布的對象必須是線程安全的,或者是有效的不可變對象(有效不可變意味著對象的狀態(tài)在發(fā)布之后永遠不會被修改)。volatile 類型的引用可以確保對象的發(fā)布形式的可見性,但是如果對象的狀態(tài)在發(fā)布后將發(fā)生更改,那么就需要額外的同步。
安全使用 volatile 的另一種簡單模式是:定期 “發(fā)布” 觀察結果供程序內部使用。例如,假設有一種環(huán)境傳感器能夠感覺環(huán)境溫度。一個后臺線程可能會每隔幾秒讀取一次該傳感器,并更新包含當前文檔的 volatile 變量。然后,其他線程可以讀取這個變量,從而隨時能夠看到最新的溫度值。
使用該模式的另一種應用程序就是收集程序的統(tǒng)計信息。清單 4 展示了身份驗證機制如何記憶最近一次登錄的用戶的名字。將反復使用lastUser 引用來發(fā)布值,以供程序的其他部分使用。
清單 4. 將 volatile 變量用于多個獨立觀察結果的發(fā)布
public class UserManager {
public volatile String lastUser;
public boolean authenticate(String user, String password) {
boolean valid = passwordIsValid(user, password);
if (valid) {
User u = new User();
activeUsers.add(u);
lastUser = user;
}
return valid;
}
}
|
![]() | ![]() .. 定價:¥45 優(yōu)惠價:¥42 更多書籍 |
![]() | ![]() .. 定價:¥225 優(yōu)惠價:¥213 更多書籍 |