国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Volatile 變量使用指南

lvzishen / 1388人閱讀

摘要:第一個條件的限制使變量不能用作線程安全計數(shù)器。模式狀態(tài)標志也許實現(xiàn)變量的規(guī)范使用僅僅是使用一個布爾狀態(tài)標志,用于指示發(fā)生了一個重要的一次性事件,例如完成初始化或請求停機。

Volatile 變量使用指南
    Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與
ynchronized 塊相比,volatile 變量所需的編碼較少,并且運行時開銷也較少,但是它所
能實現(xiàn)的功能也僅是 synchronized 的一部分。本文介紹了幾種有效使用 volatile 變量
的模式,并強調(diào)了幾種不適合使用 volatile 變量的情形。
    鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。互
斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現(xiàn)對共享數(shù)據(jù)的協(xié)調(diào)訪問
協(xié)議,這樣,一次就只有一個線程能夠使用該共享數(shù)據(jù)。可見性要更加復雜一些,它必須確
保釋放鎖之前對共享數(shù)據(jù)做出的更改對于隨后獲得該鎖的另一個線程是可見的 —— 如果沒有
同步機制提供的這種可見性保證,線程看到的共享變量可能是修改前的值或不一致的值,這
將引發(fā)許多嚴重問題。
Volatile 變量
    Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說
線程能夠自動發(fā)現(xiàn) volatile 變量的最新值。Volatile 變量可用于提供線程安全,但是
只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有
約束。因此,多帶帶使用 volatile 還不足以實現(xiàn)計數(shù)器、互斥鎖或任何具有與多個變量相
關(guān)的不變式(Invariants)的類(例如 “start <=end”)。
    出于簡易性或可伸縮性的考慮,您可能傾向于使用 volatile 變量而不是鎖。當使用
volatile變量而非鎖時,某些習慣用法(idiom)更加易于編碼和閱讀。此外,volatile 
變量不會像鎖那樣造成線程阻塞,因此也很少造成可伸縮性問題。在某些情況下,如果讀操
作遠遠大于寫操作,volatile 變量還可以提供優(yōu)于鎖的性能優(yōu)勢。
正確使用 volatile 變量的條件

您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線
程安全,必須同時滿足下面兩個條件:

  - 對變量的寫操作不依賴于當前值。
  - 該變量沒有包含在具有其他變量的不變式中。
  
    實際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀
態(tài),包括變量的當前狀態(tài)。
    第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器。雖然增量操作(x++)
看上去類似一個多帶帶操作,實際上它是一個由讀取-修改-寫入操作序列組成的組合操作,必
須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性。實現(xiàn)正確的操作需要使 x 的值
在操作期間保持不變,而 volatile 變量無法實現(xiàn)這點。(然而,如果將值調(diào)整為只從單個線
程寫入,那么可以忽略第一個條件。)
    大多數(shù)編程情形都會與這兩個條件的其中之一沖突,使得 volatile 變量不能像
 synchronized 那樣普遍適用于實現(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
類型不能夠充分實現(xiàn)類的線程安全;從而仍然需要使用同步。否則,如果湊巧兩個線程在同
一時間使用不一致的值執(zhí)行 setLower 和 setUpper 的話,則會使范圍處于不一致的狀態(tài)。
例如,如果初始狀態(tài)是 (0, 5),同一時間內(nèi),線程 A 調(diào)用 setLower(4) 并且線程 B 調(diào)
用 setUpper(3),顯然這兩個操作交叉存入的值是不符合條件的,那么兩個線程都會通過用
于保護不變式的檢查,使得最后的范圍值是 (4, 3) —— 一個無效值。至于針對范圍的其他
操作,我們需要使 setLower() 和 setUpper() 操作原子化 —— 而將字段定義為 volatile 
類型是無法實現(xiàn)這一目的的。
性能考慮
      使用 volatile 變量的主要原因是其簡易性:在某些情形下,使用 volatile 變量
要比使用相應(yīng)的鎖簡單得多。使用 volatile 變量次要原因是其性能:某些情況下,volatile 
變量同步機制的性能要優(yōu)于鎖。
      很難做出準確、全面的評價,例如 “X 總是比 Y 快”,尤其是對 JVM 內(nèi)在的操作而
言。(例如,某些情況下 VM 也許能夠完全刪除鎖機制,這使得我們難以抽象地比較 volatile 
和 synchronized 的開銷。)就是說,在目前大多數(shù)的處理器架構(gòu)上,volatile 讀操作開
銷非常低 —— 幾乎和非 volatile 讀操作一樣。而 volatile 寫操作的開銷要比非volatile
寫操作多很多,因為要保證可見性需要實現(xiàn)內(nèi)存界定(Memory Fence),即便如此,volatile
的總開銷仍然要比鎖獲取低。
      volatile 操作不會像鎖一樣造成阻塞,因此,在能夠安全使用 volatile 的情況下,
volatile 可以提供一些優(yōu)于鎖的可伸縮特性。如果讀操作的次數(shù)要遠遠超過寫操作,與鎖相
比,volatile 變量通常能夠減少同步的性能開銷。
正確使用 volatile 的模式
      很多并發(fā)性專家事實上往往引導用戶遠離 volatile 變量,因為使用它們要比使用鎖
更加容易出錯。然而,如果謹慎地遵循一些良好定義的模式,就能夠在很多場合內(nèi)安全地使用
volatile 變量。要始終牢記使用 volatile 的限制 —— 只有在狀態(tài)真正獨立于程序內(nèi)其他
內(nèi)容時才能使用 volatile —— 這條規(guī)則能夠避免將這些模式擴展到不安全的用例。
模式 #1:狀態(tài)標志
      也許實現(xiàn) volatile 變量的規(guī)范使用僅僅是使用一個布爾狀態(tài)標志,用于指示發(fā)生了一
個重要的一次性事件,例如完成初始化或請求停機。
      很多應(yīng)用程序包含了一種控制結(jié)構(gòu),形式為 “在還沒有準備好停止程序時再執(zhí)行一些工
作”,如清單 2 所示:

清單 2. 將 volatile 變量作為狀態(tài)標志使用

volatile boolean shutdownRequested;

...

public void shutdown() { shutdownRequested = true; }

public void doWork() { 
    while (!shutdownRequested) { 
        // do stuff
    }
}

很可能會從循環(huán)外部調(diào)用 shutdown() 方法 —— 即在另一個線程中 —— 因此,需要執(zhí)行某種同
步來確保正確實現(xiàn) shutdownRequested 變量的可見性。(可能會從 JMX 偵聽程序、GUI 事件線
程中的操作偵聽程序、通過 RMI 、通過一個 Web 服務(wù)等調(diào)用)。然而,使用 synchronized 塊
編寫循環(huán)要比使用清單 2 所示的 volatile 狀態(tài)標志編寫麻煩很多。由于volatile 簡化了編
碼,并且狀態(tài)標志并不依賴于程序內(nèi)任何其他狀態(tài),因此此處非常適合使用 volatile。

這種類型的狀態(tài)標記的一個公共特性是:通常只有一種狀態(tài)轉(zhuǎn)換;shutdownRequested 標志從
false 轉(zhuǎn)換為 true,然后程序停止。這種模式可以擴展到來回轉(zhuǎn)換的狀態(tài)標志,但是只有在轉(zhuǎn)換
周期不被察覺的情況下才能擴展(從 false 到 true,再轉(zhuǎn)換到 false)。此外,還需要某些原
子狀態(tài)轉(zhuǎn)換機制,例如原子變量。

模式 #2:一次性安全發(fā)布(one-time safe publication)
      缺乏同步會導致無法實現(xiàn)可見性,這使得確定何時寫入對象引用而不是原語值變得更
加困難。在缺乏同步的情況下,可能會遇到某個對象引用的更新值(由另一個線程寫入)和
該對象狀態(tài)的舊值同時存在。(這就是造成著名的雙重檢查鎖定(double-checked-locking)
問題的根源,其中對象引用在沒有同步的情況下進行讀操作,產(chǎn)生的問題是您可能會看到一
個更新的引用,但是仍然會通過該引用看到不完全構(gòu)造的對象)。
      實現(xiàn)安全發(fā)布對象的一種技術(shù)就是將對象引用定義為 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 的引用時,將會得到一個不完全構(gòu)造的 Flooble。
      該模式的一個必要條件是:被發(fā)布的對象必須是線程安全的,或者是有效的不可變對
象(有效不可變意味著對象的狀態(tài)在發(fā)布之后永遠不會被修改)。volatile 類型的引用可以
確保對象的發(fā)布形式的可見性,但是如果對象的狀態(tài)在發(fā)布后將發(fā)生更改,那么就需要額外的
同步。
模式 #3:獨立觀察(independent observation)
      安全使用 volatile 的另一種簡單模式是:定期 “發(fā)布” 觀察結(jié)果供程序內(nèi)部使用。
例如,假設(shè)有一種環(huán)境傳感器能夠感覺環(huán)境溫度。一個后臺線程可能會每隔幾秒讀取一次該
傳感器,并更新包含當前文檔的 volatile 變量。然后,其他線程可以讀取這個變量,從而
隨時能夠看到最新的溫度值。
      使用該模式的另一種應(yīng)用程序就是收集程序的統(tǒng)計信息。清單 4 展示了身份驗證機制
如何記憶最近一次登錄的用戶的名字。將反復使用 lastUser 引用來發(fā)布值,以供程序的其
他部分使用。

清單 4. 將 volatile 變量用于多個獨立觀察結(jié)果的發(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;
    }
}
      該模式是前面模式的擴展;將某個值發(fā)布以在程序內(nèi)的其他地方使用,但是與一次性
事件的發(fā)布不同,這是一系列獨立事件。這個模式要求被發(fā)布的值是有效不可變的 —— 即值
的狀態(tài)在發(fā)布后不會更改。使用該值的代碼需要清楚該值可能隨時發(fā)生變化。
模式 #4:“volatile bean” 模式
      volatile bean 模式適用于將 JavaBeans 作為“榮譽結(jié)構(gòu)”使用的框架。在volatile
bean 模式中,JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器。
volatile bean 模式的基本原理是:很多框架為易變數(shù)據(jù)的持有者(例如 HttpSession)提
供了容器,但是放入這些容器中的對象必須是線程安全的。
      在 volatile bean 模式中,JavaBean 的所有數(shù)據(jù)成員都是 volatile 類型的,并且
getter 和 setter 方法必須非常普通 —— 除了獲取或設(shè)置相應(yīng)的屬性外,不能包含任何邏輯。
此外,對于對象引用的數(shù)據(jù)成員,引用的對象必須是有效不可變的。(這將禁止具有數(shù)組值的屬
性,因為當數(shù)組引用被聲明為 volatile 時,只有引用而不是數(shù)組本身具有 volatile 語義)。
對于任何 volatile 變量,不變式或約束都不能包含 JavaBean 屬性。清單 5 中的示例展示
了遵守 volatile bean 模式的 JavaBean:

清單 5. 遵守 volatile bean 模式的 Person 對象

@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 的高級模式
     前面幾節(jié)介紹的模式涵蓋了大部分的基本用例,在這些模式中使用 volatile 非常有用
并且簡單。這一節(jié)將介紹一種更加高級的模式,在該模式中,volatile 將提供性能或可伸縮
性優(yōu)勢。
     volatile 應(yīng)用的的高級模式非常脆弱。因此,必須對假設(shè)的條件仔細證明,并且這些模
式被嚴格地封裝了起來,因為即使非常小的更改也會損壞您的代碼!同樣,使用更高級的volatile
用例的原因是它能夠提升性能,確保在開始應(yīng)用高級模式之前,真正確定需要實現(xiàn)這種性能獲益。
需要對這些模式進行權(quán)衡,放棄可讀性或可維護性來換取可能的性能收益 —— 如果您不需要提升
性能(或者不能夠通過一個嚴格的測試程序證明您需要它),那么這很可能是一次糟糕的交易,
因為您很可能會得不償失,換來的東西要比放棄的東西價值更低。
模式 #5:開銷較低的讀-寫鎖策略
     目前為止,您應(yīng)該了解了 volatile 的功能還不足以實現(xiàn)計數(shù)器。因為 ++x 實際上是三
種操作(讀、添加、存儲)的簡單組合,如果多個線程湊巧試圖同時對 volatile 計數(shù)器執(zhí)行
增量操作,那么它的更新值有可能會丟失。
     然而,如果讀操作遠遠超過寫操作,您可以結(jié)合使用內(nèi)部鎖和 volatile 變量來減少公共
代碼路徑的開銷。清單 6 中顯示的線程安全的計數(shù)器使用 synchronized 確保增量操作是原子
的,并使用 volatile 保證當前結(jié)果的可見性。如果更新不頻繁的話,該方法可實現(xiàn)更好的性能,
因為讀路徑的開銷僅僅涉及 volatile 讀操作,這通常要優(yōu)于一個無競爭的鎖獲取的開銷。

清單 6. 結(jié)合使用 volatile 和 synchronized 實現(xiàn) “開銷較低的讀-寫鎖”

@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++;
    }
}
     之所以將這種技術(shù)稱之為 “開銷較低的讀-寫鎖” 是因為您使用了不同的同步機制進行讀
寫操作。因為本例中的寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile
安全地實現(xiàn)計數(shù)器 —— 您必須使用鎖。然而,您可以在讀操作中使用 volatile 確保當前值的
可見性,因此可以使用鎖進行所有變化的操作,使用 volatile 進行只讀操作。其中,鎖一次
只允許一個線程訪問值,volatile 允許多個線程執(zhí)行讀操作,因此當使用 volatile 保證讀
代碼路徑時,要比使用鎖執(zhí)行全部代碼路徑獲得更高的共享度 —— 就像讀-寫操作一樣。然而,
要隨時牢記這種模式的弱點:如果超越了該模式的最基本應(yīng)用,結(jié)合這兩個競爭的同步機制將
變得非常困難。
結(jié)束語
     與鎖相比,Volatile 變量是一種非常簡單但同時又非常脆弱的同步機制,它在某些情況
下將提供優(yōu)于鎖的性能和伸縮性。如果嚴格遵循 volatile 的使用條件 —— 即變量真正獨立于
其他變量和自己以前的值 —— 在某些情況下可以使用 volatile 代替 synchronized 來簡化
代碼。然而,使用 volatile 的代碼往往比使用鎖的代碼更加容易出錯。本文介紹的模式涵蓋
了可以使用 volatile 代替 synchronized 的最常見的一些用例。遵循這些模式(注意使用
時不要超過各自的限制)可以幫助您安全地實現(xiàn)大多數(shù)用例,使用 volatile 變量獲得更佳性
能。


原文鏈接
如果有侵權(quán),馬上刪除

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/69631.html

相關(guān)文章

  • 深入理解Java內(nèi)存模型(四)—— volatile

    摘要:前情提要深入理解內(nèi)存模型三順序一致性的特性當我們聲明共享變量為后,對這個變量的讀寫將會很特別。當讀線程的數(shù)量大大超過寫線程時,選擇在寫之后插入屏障將帶來可觀的執(zhí)行效率的提升。 前情提要 深入理解Java內(nèi)存模型(三)——順序一致性 volatile的特性 當我們聲明共享變量為volatile后,對這個變量的讀/寫將會很特別。理解volatile特性的一個好方法是:把對volatil...

    K_B_Z 評論0 收藏0
  • java volatile 關(guān)鍵字

    摘要:舉個例子,在多線程不使用環(huán)境中,每個線程會從主存中復制變量到緩存以提高性能。保證了變量的可見性關(guān)鍵字解決了變量的可見性問題。在多線程同時共享變量的情形下,關(guān)鍵字已不足以保證程序的并發(fā)性。 volatile 關(guān)鍵字能把 Java 變量標記成被存儲到主存中。這表示每一次讀取 volatile 變量都會訪問計算機主存,而不是 CPU 緩存。每一次對 volatile 變量的寫操作不僅會寫到 ...

    scola666 評論0 收藏0
  • Java中的Volatile關(guān)鍵字

    摘要:變量可見性問題的關(guān)鍵字保證了多個線程對變量值變化的可見性。只要一個線程需要首先讀取一個變量的值,基于這個值生成一個新值,則一個關(guān)鍵字不足以保證正確的可見性。 Java的volatile關(guān)鍵字用于標記一個Java變量為在主存中存儲。更確切的說,對volatile變量的讀取會從計算機的主存中讀取,而不是從CPU緩存中讀取,對volatile變量的寫入會寫入到主存中,而不只是寫入到CPU緩存...

    JohnLui 評論0 收藏0
  • Java內(nèi)存模型中volatile的內(nèi)存語義及對同步的作用

    摘要:一接觸內(nèi)存模型中的實例靜態(tài)變量以及數(shù)組都存儲在堆內(nèi)存中,可在線程之間共享。所以,在編碼上實現(xiàn)鎖的內(nèi)存語義,可以通過對一個變量的讀寫,來實現(xiàn)線程之間相互通知,保證臨界區(qū)域代碼的互斥執(zhí)行。 原文發(fā)表于我的博客 volatile關(guān)鍵字: 使用volatile關(guān)鍵字修飾的的變量,總能看到任意線程對它最后的寫入,即總能保證任意線程在讀寫volatile修飾的變量時,總是從內(nèi)存中讀取最新的值。以下...

    QLQ 評論0 收藏0
  • 慕課網(wǎng)_《細說Java多線程之內(nèi)存可見性》學習總結(jié)

    時間:2017年07月09日星期日說明:本文部分內(nèi)容均來自慕課網(wǎng)。@慕課網(wǎng):http://www.imooc.com教學源碼:無學習源碼:https://github.com/zccodere/s... 第一章:課程簡介 1-1 課程簡介 課程目標和學習內(nèi)容 共享變量在線程間的可見性 synchronized實現(xiàn)可見性 volatile實現(xiàn)可見性 指令重排序 as-if-seria...

    wupengyu 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<