Java並發之volatile二

來源:互聯網
上載者:User

標籤:vol   out   bool   auth   利用   vertica   font   對象   獨立   

使用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:

@ThreadSafepublic 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;">@ThreadSafepublic 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二

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.