volatile關(guān)鍵字

參考鏈接:http://www.infoq.com/cn/articles/java-memory-model-4/
http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
http://blog.csdn.net/feier7501/article/details/20001083

在Java的內(nèi)存模式下,線程就把變量保存到本地的內(nèi)存中,不直接和主存進行讀寫。這樣就可能會出現(xiàn)有一個線程在貯存中修改了某變量,但是另一個線程讀取的是它之前在本地內(nèi)存里的拷貝值。這樣就會造成數(shù)據(jù)不一致了。

volatile寫-讀的內(nèi)存語義#

volatile寫的內(nèi)存語義如下:

當寫一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量刷新到主內(nèi)存。

假設(shè)線程A首先執(zhí)行writer()方法,隨后線程B執(zhí)行reader()方法,初始時兩個線程的本地內(nèi)存中的flag和a都是初始狀態(tài)。下圖是線程A執(zhí)行volatile寫后,共享變量的狀態(tài)示意圖:

線程A在寫flag變量后,本地內(nèi)存A中被線程A更新過的兩個共享變量的值被刷新到主內(nèi)存中。此時,本地內(nèi)存A和主內(nèi)存中的共享變量的值是一致的。

volatile讀的內(nèi)存語義如下:

當讀一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
下面是線程B讀同一個volatile變量后,共享變量的狀態(tài)示意圖:

如上圖所示,在讀flag變量后,本地內(nèi)存B已經(jīng)被置為無效。此時,線程B必須從主內(nèi)存中讀取共享變量。線程B的讀取操作將導致本地內(nèi)存B與主內(nèi)存中的共享變量的值也變成一致的了。

如果我們把volatile寫和volatile讀這兩個步驟綜合起來看的話,在讀線程B讀一個volatile變量后,寫線程A在寫這個volatile變量之前所有可見的共享變量的值都將立即變得對讀線程B可見。

我以前看了這些之后就一直以為volatile是原子性的,可以實現(xiàn)線程同步的,事實上并不是這樣的!!##

Volatile 變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當前值與修改后值之間沒有約束。因此,單獨使用 volatile 還不足以實現(xiàn)計數(shù)器、互斥鎖或任何具有與多個變量相關(guān)的不變式(Invariants)的類(例如 “start <=end”)。

正確使用 volatile 變量的條件#

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

1)對變量的寫操作不依賴于當前值。
2)該變量沒有包含在具有其他變量的不變式中。

實際上,這些條件表明,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態(tài),包括變量的當前狀態(tài)。

第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器。雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀?。薷模瓕懭氩僮餍蛄薪M成的組合操作,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性。實現(xiàn)正確的操作需要使 x 的值在操作期間保持不變,而 volatile 變量無法實現(xiàn)這點。(然而,如果將值調(diào)整為只從單個線程寫入,那么可以忽略第一個條件。)

大多數(shù)編程情形都會與這兩個條件的其中之一沖突,使得 volatile 變量不能像 synchronized 那樣普遍適用于實現(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 變量通常能夠減少同步的性能開銷。

原文地址是:http://www.ibm.com/developerworks/cn/java/j-jtp06197.html
上鏈接原文里提到了很多模式(而我還沒有明白的看懂:-O)

關(guān)于volatile的重排列#

為了實現(xiàn)volatile的內(nèi)存語義,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序。對于編譯器來說,發(fā)現(xiàn)一個最優(yōu)布置來最小化插入屏障的總數(shù)幾乎不可能,為此,JMM采取保守策略。下面是基于保守策略的JMM內(nèi)存屏障插入策略:

在每個volatile寫操作的前面插入一個StoreStore屏障。
在每個volatile寫操作的后面插入一個StoreLoad屏障。
在每個volatile讀操作的前面插入一個LoadLoad屏障。
在每個volatile讀操作的后面插入一個LoadStore屏障。
上述內(nèi)存屏障插入策略非常保守,但它可以保證在任意處理器平臺,任意的程序中都能得到正確的volatile內(nèi)存語義。

就是這樣:

StoreStore屏障在volatile寫之前,這樣前面的所有普通寫操作已經(jīng)對任意處理器可見了。這是因為StoreStore屏障將保障上面所有的普通寫在volatile寫之前刷新到主內(nèi)存。

這里比較有意思的是volatile寫后面的StoreLoad屏障。這個屏障的作用是避免volatile寫與后面可能有的volatile讀/寫操作重排序。因為編譯器常常無法準確判斷在一個volatile寫的后面,是否需要插入一個StoreLoad屏障(比如,一個volatile寫之后方法立即return)。為了保證能正確實現(xiàn)volatile的內(nèi)存語義,JMM在這里采取了保守策略:在每個volatile寫的后面或在每個volatile讀的前面插入一個StoreLoad屏障。從整體執(zhí)行效率的角度考慮,JMM選擇了在每個volatile寫的后面插入一個StoreLoad屏障。因為volatile寫-讀內(nèi)存語義的常見使用模式是:一個寫線程寫volatile變量,多個讀線程讀同一個volatile變量。當讀線程的數(shù)量大大超過寫線程時,選擇在volatile寫之后插入StoreLoad屏障將帶來可觀的執(zhí)行效率的提升。從這里我們可以看到JMM在實現(xiàn)上的一個特點:首先確保正確性,然后再去追求執(zhí)行效率。

上圖中的LoadLoad屏障用來禁止處理器把上面的volatile讀與下面的普通讀重排序。LoadStore屏障用來禁止處理器把上面的volatile讀與下面的普通寫重排序。

這里有一段示例代碼:

class VolatileBarrierExample {
    int a;
    volatile int v1 = 1;
    volatile int v2 = 2;

    void readAndWrite() {
        int i = v1;           //第一個volatile讀
        int j = v2;           // 第二個volatile讀
        a = i + j;            //普通寫
        v1 = i + 1;          // 第一個volatile寫
        v2 = j * 2;          //第二個 volatile寫
    }

    …                    //其他方法
}

針對readAndWrite()方法,編譯器在生成字節(jié)碼時可以做如下的優(yōu)化:

注意,最后的StoreLoad屏障不能省略。因為第二個volatile寫之后,方法立即return。此時編譯器可能無法準確斷定后面是否會有volatile讀或?qū)?,為了安全起見,編譯器常常會在這里插入一個StoreLoad屏障。

以上大多數(shù)內(nèi)容都來自http://www.infoq.com/cn/articles/java-memory-model-4/#anch95647
博主寫的尤為精彩,尤其是評論里的交流也值得一看。

在評論 區(qū)里看到一個 解答了自己的困惑:

當讀一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。#

問題:設(shè)線程A和B共享變量x,線程B和C共享變量y,x和y是非volatile的,A和B線程之間共享volatile變量v,那么當B讀取v的時候,B線程的本地內(nèi)存里面的x被設(shè)為無效了,這點我理解,問題是,y是否也會被設(shè)為無效從而需要到主存中重新讀?。?/p>

以下為博主的回答:

y也會被設(shè)為無效,從而需要到主存中重新讀取.

其實:本地內(nèi)存,主內(nèi)存,設(shè)置本地內(nèi)存為無效,從主內(nèi)存中去讀取值。這些都是為了讓讀者更形象生動的理解java內(nèi)存模型而虛構(gòu)出來的,并不真實存在。
對于你的問題,可以從volatile的編譯器重排序規(guī)則和volatile的處理器內(nèi)存屏障插入策略中找到答案。比如下面的程序代碼:

int i = volatile; //1,volatile讀
int j = a; //2,普通讀(假設(shè)a為普通共享變量)

在這里,不管a是在哪些線程之間共享,volatile的編譯器重排序規(guī)則和volatile的處理器內(nèi)存屏障插入策略都會禁止2重排序到1的前面。

關(guān)于好多JMM,甚至volatile本身我也沒有百分百看懂 。此篇僅為學習記錄,存在大量知識是我摘抄的~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • volatile 關(guān)鍵字解析 原文出處: 海子volatile 這個關(guān)鍵字可能很多朋友都聽說過,或許也都用過。在 ...
    常青大俠閱讀 668評論 0 4
  • 文.孫亮好想給你一絲溫柔把自己化作秋風去輕輕的挽住你的手你在左我漫步在右我就像滑落的樹葉把所有的真情獻給大地一分不...
    朦朧詩人孫亮閱讀 559評論 1 18
  • 寫在前面 目前我正在寫成都18樓的小仙滇-藏-川游記,這次旅程已經(jīng)結(jié)束,這次旅行時間是從3月5號開始,一直到4月1...
    長腳溫柔閱讀 1,084評論 10 3
  • 哎,我是不是有社交障礙啊?我不是一個主動的人,不會積極地與別人攀談聊天,除非工作上有聯(lián)系,否則只會呆在自己的圈子里...
    Sara_馨閱讀 305評論 0 0
  • abstract 抽象的abstract base class (ABC)抽象基類abstract class...
    碼蟻Q閱讀 1,131評論 3 29

友情鏈接更多精彩內(nèi)容