參考鏈接: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)存中,不直接和主存進(jìn)行讀寫感论。這樣就可能會出現(xiàn)有一個線程在貯存中修改了某變量奈揍,但是另一個線程讀取的是它之前在本地內(nèi)存里的拷貝值曲尸。這樣就會造成數(shù)據(jù)不一致了。
volatile寫-讀的內(nèi)存語義#
volatile寫的內(nèi)存語義如下:
當(dāng)寫一個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)存語義如下:
當(dāng)讀一個volatile變量時纤泵,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量话原。
下面是線程B讀同一個volatile變量后夕吻,共享變量的狀態(tài)示意圖:
如上圖所示,在讀flag變量后繁仁,本地內(nèi)存B已經(jīng)被置為無效涉馅。此時,線程B必須從主內(nèi)存中讀取共享變量黄虱。線程B的讀取操作將導(dǎo)致本地內(nèi)存B與主內(nèi)存中的共享變量的值也變成一致的了稚矿。
如果我們把volatile寫和volatile讀這兩個步驟綜合起來看的話,在讀線程B讀一個volatile變量后捻浦,寫線程A在寫這個volatile變量之前所有可見的共享變量的值都將立即變得對讀線程B可見晤揣。
我以前看了這些之后就一直以為volatile是原子性的,可以實現(xiàn)線程同步的朱灿,事實上并不是這樣的C潦丁!##
Volatile 變量可用于提供線程安全盗扒,但是只能應(yīng)用于非常有限的一組用例:多個變量之間或者某個變量的當(dāng)前值與修改后值之間沒有約束跪楞。因此,單獨使用 volatile 還不足以實現(xiàn)計數(shù)器侣灶、互斥鎖或任何具有與多個變量相關(guān)的不變式(Invariants)的類(例如 “start <=end”)甸祭。
正確使用 volatile 變量的條件#
只能在有限的一些情形下使用 volatile 變量替代鎖(如synchronized)。要使 volatile 變量提供理想的線程安全褥影,必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴于當(dāng)前值池户。
2)該變量沒有包含在具有其他變量的不變式中。
實際上凡怎,這些條件表明校焦,可以被寫入 volatile 變量的這些有效值獨立于任何程序的狀態(tài),包括變量的當(dāng)前狀態(tài)栅贴。
第一個條件的限制使 volatile 變量不能用作線程安全計數(shù)器斟湃。雖然增量操作(x++)看上去類似一個單獨操作,實際上它是一個由讀乳苁怼-修改-寫入操作序列組成的組合操作凝赛,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性坛缕。實現(xiàn)正確的操作需要使 x 的值在操作期間保持不變墓猎,而 volatile 變量無法實現(xiàn)這點。(然而赚楚,如果將值調(diào)整為只從單個線程寫入毙沾,那么可以忽略第一個條件。)
大多數(shù)編程情形都會與這兩個條件的其中之一沖突宠页,使得 volatile 變量不能像 synchronized 那樣普遍適用于實現(xiàn)線程安全左胞。
性能考慮#
使用 volatile 變量的主要原因是其簡易性:
在某些情形下寇仓,使用 volatile 變量要比使用相應(yīng)的鎖簡單得多。使用 volatile 變量次要原因是其性能:某些情況下烤宙,volatile 變量同步機(jī)制的性能要優(yōu)于鎖遍烦。
很難做出準(zhǔn)確、全面的評價躺枕,例如 “X 總是比 Y 快”服猪,尤其是對 JVM 內(nèi)在的操作而言。(例如拐云,某些情況下 VM 也許能夠完全刪除鎖機(jī)制罢猪,這使得我們難以抽象地比較 volatile 和 synchronized 的開銷。)
就是說叉瘩,在目前大多數(shù)的處理器架構(gòu)上膳帕,volatile 讀操作開銷非常低 —— 幾乎和非 volatile 讀操作一樣。而 volatile 寫操作的開銷要比非 volatile 寫操作多很多房揭,因為要保證可見性需要實現(xiàn)內(nèi)存界定(Memory Fence)备闲,即便如此,volatile 的總開銷仍然要比鎖獲取低捅暴。
volatile 操作不會像鎖一樣造成阻塞恬砂,因此,在能夠安全使用 volatile 的情況下蓬痒,volatile 可以提供一些優(yōu)于鎖的可伸縮特性泻骤。
如果讀操作的次數(shù)要遠(yuǎn)遠(yuǎn)超過寫操作,與鎖相比梧奢,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讀/寫操作重排序洞渤。因為編譯器常常無法準(zhǔn)確判斷在一個volatile寫的后面,是否需要插入一個StoreLoad屏障(比如属瓣,一個volatile寫之后方法立即return)。為了保證能正確實現(xiàn)volatile的內(nèi)存語義讯柔,JMM在這里采取了保守策略:在每個volatile寫的后面或在每個volatile讀的前面插入一個StoreLoad屏障抡蛙。從整體執(zhí)行效率的角度考慮,JMM選擇了在每個volatile寫的后面插入一個StoreLoad屏障魂迄。因為volatile寫-讀內(nèi)存語義的常見使用模式是:一個寫線程寫volatile變量粗截,多個讀線程讀同一個volatile變量。當(dāng)讀線程的數(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蜂奸。此時編譯器可能無法準(zhǔn)確斷定后面是否會有volatile讀或?qū)懀瑸榱税踩鹨娪财迹幾g器常常會在這里插入一個StoreLoad屏障扩所。
以上大多數(shù)內(nèi)容都來自http://www.infoq.com/cn/articles/java-memory-model-4/#anch95647
博主寫的尤為精彩,尤其是評論里的交流也值得一看朴乖。
在評論 區(qū)里看到一個 解答了自己的困惑:
當(dāng)讀一個volatile變量時祖屏,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。#
問題:設(shè)線程A和B共享變量x寒砖,線程B和C共享變量y赐劣,x和y是非volatile的,A和B線程之間共享volatile變量v哩都,那么當(dāng)B讀取v的時候魁兼,B線程的本地內(nèi)存里面的x被設(shè)為無效了,這點我理解,問題是咐汞,y是否也會被設(shè)為無效從而需要到主存中重新讀雀呛簟?
以下為博主的回答:
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本身我也沒有百分百看懂 做祝。此篇僅為學(xué)習(xí)記錄砾省,存在大量知識是我摘抄的~