參考鏈接: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本身我也沒有百分百看懂 。此篇僅為學習記錄,存在大量知識是我摘抄的~