1. 程式人生 > >Java並發之volatile二

Java並發之volatile二

inb 有一種 情況 適用於 次數 align 可變 應用程序 new

使用volatilekeyword的場景

Volatile 變量具有 synchronized 的可見性特性。可是不具備原子特性。這就是說線程可以自己主動發現 volatile 變量的最新值。Volatile 變量可用於提供線程安全,可是僅僅能應用於很有限的一組用例:多個變量之間或者某個變量的當前值與改動後值之間沒有約束。

因此。單獨使用 volatile 還不足以實現計數器、相互排斥鎖或不論什麽具有與多個變量相關的不變式(Invariants)的類(比如 “start <=end”)。
出於簡易性或可伸縮性的考慮,您可能傾向於使用 volatile 變量而不是鎖。當使用 volatile 變量而非鎖時,某些習慣使用方法(idiom)更加易於編碼和閱讀。

此外。volatile 變量不會像鎖那樣造成線程堵塞。因此也非常少造成可伸縮性問題。在某些情況下,假設讀操作遠遠大於寫操作,volatile 變量還能夠提供優於鎖的性能優勢。
正確使用 volatile 變量的條件
您僅僅能在有限的一些情形下使用 volatile 變量替代鎖。

要使 volatile 變量提供理想的線程安全,必須同一時候滿足以下兩個條件:
(1)對變量的寫操作不依賴於當前值。
(2)該變量沒有包括在具有其它變量的不變式中。

實際上,這些條件表明。能夠被寫入 volatile 變量的這些有效值獨立於不論什麽程序的狀態,包含變量的當前狀態。


第一個條件的限制使 volatile 變量不能用作線程安全計數器。

盡管增量操作(x++)看上去類似一個單獨操作。實際上它是一個由讀取-改動-寫入操作序列組成的組合操作。必須以原子方式運行,而 volatile 不能提供必須的原子特性。實現正確的操作須要使 x 的值在操作期間保持不變。而 volatile 變量無法實現這點。(然而,假設將值調整為僅僅從單個線程寫入。那麽能夠忽略第一個條件。


大多數編程情形都會與這兩個條件的當中之中的一個沖突,使得 volatile 變量不能像 synchronized 那樣普遍適用於實現線程安全。

性能上:使用 volatile 變量要比使用對應的鎖簡單得多。在眼下大多數的處理器架構上。volatile 讀操作開銷非常低 —— 差點兒和非 volatile 讀操作一樣。而 volatile 寫操作的開銷要比非 volatile 寫操作多非常多,由於要保證可見性須要實現內存界定(Memory Fence)。即便如此,volatile 的總開銷仍然要比鎖獲取低。


volatile 操作不會像鎖一樣造成堵塞,因此。在可以安全使用 volatile 的情況下,volatile 可以提供一些優於鎖的可伸縮特性。

假設讀操作的次數要遠遠超過寫操作。與鎖相比,volatile 變量通常可以降低同步的性能開銷。


Java中使用volatile的幾個場景:

1.狀態標記量

volatile boolean shutdownRequested;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}
非常可能會從循環外部調用 shutdown() 方法 —— 即在還有一個線程中 —— 因此,須要運行某種同步來確保正確實現 shutdownRequested 變量的可見性。(可能會從 JMX 偵聽程序、GUI 事件線程中的操作偵聽程序、通過 RMI 、通過一個 Web 服務等調用)。

然而。使用 synchronized 塊編寫循環要比使用清單 2 所看到的的 volatile 狀態標誌編寫麻煩非常多。因為 volatile 簡化了編碼,而且狀態標誌並不依賴於程序內不論什麽其它狀態。因此此處很適合使用 volatile。


這樣的類型的狀態標記的一個公共特性是:通常僅僅有一種狀態轉換;shutdownRequested 標誌從 false 轉換為 true。然後程序停止。這樣的模式能夠擴展到來回轉換的狀態標誌,可是僅僅有在轉換周期不被察覺的情況下才幹擴展(從 false 到 true,再轉換到 false)。此外。還須要某些原子狀態轉換機制。比如原子變量。


2一次性安全公布

缺乏同步會導致無法實現可見性,這使得確定何時寫入對象引用而不是原語值變得更加困難。在缺乏同步的情況下,可能會遇到某個對象引用的更新值(由還有一個線程寫入)和該對象狀態的舊值同一時候存在。(這就是造成著名的雙重檢查鎖定(double-checked-locking)問題的根源。當中對象引用在沒有同步的情況下進行讀操作。產生的問題是您可能會看到一個更新的引用,可是仍然會通過該引用看到不全然構造的對象)。


實現安全公布對象的一種技術就是將對象引用定義為 volatile 類型。 展示了一個演示樣例,當中後臺線程在啟動階段從數據庫載入一些數據。

其它代碼在可以利用這些數據時,在使用之前將檢查這些數據是否以前公布過。

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。


該模式的一個必要條件是:被公布的對象必須是線程安全的。或者是有效的不可變對象(有效不可變意味著對象的狀態在公布之後永遠不會被改動)。

volatile 類型的引用能夠確保對象的公布形式的可見性,可是假設對象的狀態在公布後將發生更改,那麽就須要額外的同步。


3、獨立觀察

安全使用 volatile 的還有一種簡單模式是:定期 “公布” 觀察結果供程序內部使用。比如,如果有一種環境傳感器可以感覺環境溫度。一個後臺線程可能會每隔幾秒讀取一次該傳感器,並更新包括當前文檔的 volatile 變量。然後,其它線程可以讀取這個變量,從而隨時可以看到最新的溫度值。
使用該模式的還有一種應用程序就是收集程序的統計信息。以下展示了身份驗證機制怎樣記憶近期一次登錄的用戶的名字。將重復使用 lastUser 引用來公布值,以供程序的其它部分使用。

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;
    }
}
該模式是前面模式的擴展。將某個值公布以在程序內的其它地方使用,可是與一次性事件的公布不同,這是一系列獨立事件。

這個模式要求被公布的值是有效不可變的 —— 即值的狀態在公布後不會更改。使用該值的代碼須要清楚該值可能隨時發生變化。



4:“volatile bean” 模式

volatile bean 模式適用於將 JavaBeans 作為“榮譽結構”使用的框架。在 volatile bean 模式中。JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器。volatile bean 模式的基本原理是:非常多框架為易變數據的持有者(比如 HttpSession)提供了容器,可是放入這些容器中的對象必須是線程安全的。
在 volatile bean 模式中,JavaBean 的全部數據成員都是 volatile 類型的,而且 getter 和 setter 方法必須很普通 —— 除了獲取或設置對應的屬性外,不能包括不論什麽邏輯。此外,對於對象引用的數據成員。引用的對象必須是有效不可變的。(這將禁止具有數組值的屬性,由於當數組引用被聲明為 volatile 時,僅僅有引用而不是數組本身具有 volatile 語義)。

對於不論什麽 volatile 變量,不變式或約束都不能包括 JavaBean 屬性。演示樣例展示了遵守 volatile bean 模式的 JavaBean:

@ThreadSafe
public class Person {
    private volatile String firstName;
    private volatile String lastName;
    private volatile int age;

    public String getFirstName() { return firstName; }
    public String getLastName() { return lastName; }
    public int getAge() { return age; }

    public void setFirstName(String firstName) { 
        this.firstName = firstName;
    }

    public void setLastName(String lastName) { 
        this.lastName = lastName;
    }

    public void setAge(int age) { 
        this.age = age;
    }
}



volatile 的高級模式
在上面這些模式中使用 volatile 很實用而且簡單。這一節將介紹一種更加高級的模式。在該模式中,volatile 將提供性能或可伸縮性優勢。
volatile 應用的的高級模式非常脆弱。因此,必須對如果的條件細致證明,而且這些模式被嚴格地封裝了起來,由於即使非常小的更改也會損壞您的代碼。相同。使用更高級的 volatile 用例的原因是它可以提升性能。確保在開始應用高級模式之前,真正確定須要實現這樣的性能獲益。

須要對這些模式進行權衡,放棄可讀性或可維護性來換取可能的性能收益 —— 如果您不須要提升性能(或者不可以通過一個嚴格的測試程序證明您須要它),那麽這非常可能是一次糟糕的交易。由於您非常可能會得不償失,換來的東西要比放棄的東西價值更低。
5、開銷較低的讀-寫鎖策略
眼下為止,您應該了解了 volatile 的功能還不足以實現計數器。

由於 ++x 實際上是三種操作(讀、加入、存儲)的簡單組合,假設多個線程湊巧試圖同一時候對 volatile 計數器運行增量操作,那麽它的更新值有可能會丟失。
然而,假設讀操作遠遠超過寫操作,您能夠結合使用內部鎖和 volatile 變量來降低公共代碼路徑的開銷。

以下代碼顯示的線程安全的計數器使用 synchronized 確保增量操作是原子的,並使用 volatile 保證當前結果的可見性。假設更新不頻繁的話。該方法可實現更好的性能,由於讀路徑的開銷只涉及 volatile 讀操作,這通常要優於一個無競爭的鎖獲取的開銷。

<span style="font-size:18px;">@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") private volatile int value;

    public int getValue() { return value; }

    public synchronized int increment() {
        return value++;
    }
}</span>

之所以將這樣的技術稱之為 “開銷較低的讀-寫鎖” 是由於您使用了不同的同步機制進行讀寫操作。由於本例中的寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile 安全地實現計數器 —— 您必須使用鎖。然而,您能夠在讀操作中使用 volatile 確保當前值的可見性,因此能夠使用鎖進行全部變化的操作。使用 volatile 進行僅僅讀操作。當中。鎖一次僅僅同意一個線程訪問值。volatile 同意多個線程運行讀操作,因此當使用 volatile 保證讀代碼路徑時,要比使用鎖運行所有代碼路徑獲得更高的共享度 —— 就像讀-寫操作一樣。然而。要隨時牢記這樣的模式的弱點:假設超越了該模式的最基本應用,結合這兩個競爭的同步機制將變得很困難。

結束語
與鎖相比,Volatile 變量是一種很easy但同一時候又很脆弱的同步機制。它在某些情況下將提供優於鎖的性能和伸縮性。假設嚴格遵循 volatile 的使用條件 —— 即變量真正獨立於其它變量和自己曾經的值 —— 在某些情況下能夠使用 volatile 取代 synchronized 來簡化代碼。然而。使用 volatile 的代碼往往比使用鎖的代碼更加easy出錯。

本文介紹的模式涵蓋了能夠使用 volatile 取代 synchronized 的最常見的一些用例。遵循這些模式(註意使用時不要超過各自的限制)能夠幫助您安全地實現大多數用例。使用 volatile 變量獲得更佳性能。



參考http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

Java並發之volatile二