java并發(fā)系列(1)——JMM內(nèi)存模型

1衅金、基礎(chǔ)與概念

(1)、共享性簿煌、互斥性典挑、原子性、可見性啦吧、有序性您觉。

http://www.cnblogs.com/paddix/p/5374810.html

(2)、JMM內(nèi)存模型——描述線程本地內(nèi)存和主內(nèi)存之間的抽象關(guān)系授滓。線程A和線程B之間通訊琳水,需要通過主內(nèi)存肆糕。

JMM屬于語言級的內(nèi)存模型,它確保在不同的編譯器和不同的處理器平臺之上在孝,通過禁止特定類型的編譯器重排序和處理器重排序诚啃,為程序員提供一致的內(nèi)存可見性保證。

注意私沮,線程本地內(nèi)存只是一個抽象概念始赎,它涵蓋了緩存、寫緩沖區(qū)仔燕、寄存器以及其他的硬件和編譯器優(yōu)化造垛。

2、重排序

在執(zhí)行程序時晰搀,為了提高性能五辽,編譯器和處理器常常會對指令做重排序。

重排序分類

(1)外恕、編譯器優(yōu)化的重排序:編譯器在不改變單線程程序語義的前提下杆逗,可以重新安排語句的執(zhí)行順序。

(2)鳞疲、指令級并行的重排序:現(xiàn)代處理器采用了指令級并行技術(shù)(Instruction-Level Parallelism罪郊,ILP)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性尚洽,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序悔橄。

(3)、內(nèi)存系統(tǒng)的重排序:由于處理器使用緩存和讀/寫緩沖區(qū)翎朱,這使得加載和存儲操作看上去可能是在亂序執(zhí)行橄维。如下圖:

重排序的原則:as-if-serial語義

as-if-serial語義的意思是:不管怎么重排序(編譯器和處理器為了提高并行度),(單線程)程序的執(zhí)行結(jié)果不能被改變拴曲。編譯器争舞、runtime和處理器都必須遵守as-if-serial語義。

為了遵守as-if-serial語義澈灼,編譯器和處理器不會對存在數(shù)據(jù)依賴關(guān)系的操作做重排序竞川。數(shù)據(jù)依賴關(guān)系如下圖所示:


as-if-serial語義只能保證單線程下,重排序引起的問題叁熔。在多線程情況下委乌,不存在數(shù)據(jù)依賴關(guān)系的重排序也會破壞程序的意圖。

單線程情況下荣回,控制依賴關(guān)系的重排序遭贸,不影響最終結(jié)果。多線程情況下心软,則可能會破壞程序的意圖壕吹。




JMM禁止重排序的措施:

(1)著蛙、對于編譯器,JMM的編譯器重排序規(guī)則會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)耳贬。

(2)踏堡、對于處理器重排序,JMM的處理器重排序規(guī)則會要求Java編譯器在生成指令序列時咒劲,插入特定類型的內(nèi)存屏障(Memory Barriers顷蟆,Intel稱之為MemoryFence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序腐魂。


從上圖可以看出:常見的處理器都允許Store-Load重排序帐偎;常見的處理器都不允許對存在數(shù)據(jù)依賴的操作做重排序。sparc-TSO和X86擁有相對較強(qiáng)的處理器內(nèi)存模型挤渔,它們僅允許對寫-讀操作做重排序(因為它們都使用了寫緩沖區(qū))肮街。

內(nèi)存屏障如上圖4種類型风题,StoreLoad Barriers是一個“全能型”的屏障判导,它同時具有其他3個屏障的效果。現(xiàn)代的多處理器大多支持該屏障(其他類型的屏障不一定被所有處理器支持)沛硅。執(zhí)行該屏障開銷會很昂貴眼刃,因為當(dāng)前處理器通常要把寫緩沖區(qū)中的數(shù)據(jù)全部刷新到內(nèi)存中(Buffer Fully Flush)。

3摇肌、happen-before

JDK5之后擂红,采用JSR-133版本的JMM內(nèi)存模型,使用hap-pens-before的概念來闡述操作之間的內(nèi)存可見性围小。在JMM中昵骤,如果一個操作執(zhí)行的結(jié)果需要對另一個操作可見,那么這兩個操作之間必須要存在happens-before關(guān)系肯适。這里提到的兩個操作既可以是在一個線程之內(nèi)变秦,也可以是在不同線程之間。happens-before規(guī)則如下:

程序順序規(guī)則:一個線程中的每個操作框舔,happens-before于該線程中的任意后續(xù)操作蹦玫。?

監(jiān)視器鎖規(guī)則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖刘绣。?

volatile變量規(guī)則:對一個volatile域的寫樱溉,happens-before于任意后續(xù)對這個volatile域的讀。?

傳遞性:如果A happens-before B纬凤,且B happens-beforeC福贞,那么A happens-before C。

start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動線程B)停士,那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作挖帘。

join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回绢馍,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。

對于Java程序員來說肠套,happens-before規(guī)則簡單易懂舰涌,它避免Java程序員為了理解JMM提供的內(nèi)存可見性保證而去學(xué)習(xí)復(fù)雜的重排序規(guī)則以及這些規(guī)則的具體實現(xiàn)方法。

4你稚、順序一致性

順序一致性內(nèi)存模型是一個理論參考模型瓷耙。順序一致性內(nèi)存模型有兩大特性:

1)一個線程中的所有操作必須按照程序的順序來執(zhí)行。

2)(不管程序是否同步)所有線程都只能看到一個單一的操作執(zhí)行順序刁赖。在順序一致性內(nèi)存模型中搁痛,每個操作都必須原子執(zhí)行且立刻對所有線程可見。

5宇弛、volatile

5-1鸡典、volatile實現(xiàn)原理

1)將當(dāng)前處理器緩存行的數(shù)據(jù)寫回到系統(tǒng)內(nèi)存。(Lock前綴指令.“緩存鎖定”枪芒,阻止兩個或以上處理器同時修改被緩存的內(nèi)存區(qū)域)

2)這個寫回內(nèi)存的操作會彻况,其他處理器“嗅探”到,使在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無效舅踪。

5-2纽甘、volatile特性

可見性:對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫入抽碌。

原子性:對任意單個volatile變量的讀/寫具有原子性悍赢,但類似于volatile++這種復(fù)合操作不具有原子性。

5-3、volatile寫-讀建立的happens-before關(guān)系

volatile的寫-讀與鎖的釋放-獲取有相同的內(nèi)存效果。

這里A線程寫一個volatile變量后准验,B線程讀同一個volatile變量冠句。A線程在寫volatile變量之前所有可見的共享變量,在B線程讀同一個volatile變量后,將立即變得對B線程可見。

5-4、volatile寫-讀的內(nèi)存語義

volatile寫的內(nèi)存語義如下:當(dāng)寫一個volatile變量時瀑梗,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存。

volatile讀的內(nèi)存語義如下:當(dāng)讀一個volatile變量時裳扯,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效抛丽。線程接下來將從主內(nèi)存中讀取共享變量。

注:關(guān)于volatile變量重排序饰豺,嚴(yán)格限制編譯器和處理器對volatile變量與普通變量的重排序亿鲜,確保volatile的寫-讀和鎖的釋放-獲取具有相同的內(nèi)存語義。

5-5、volatile和鎖的區(qū)別

由于volatile僅僅保證對單個volatile變量的讀/寫具有原子性蒿柳,而鎖的互斥執(zhí)行的特性可以確保對整個臨界區(qū)代碼的執(zhí)行具有原子性饶套。在功能上,鎖比volatile更強(qiáng)大垒探;在可伸縮性和執(zhí)行性能上妓蛮,volatile更有優(yōu)勢。

volatile的不能完全取代Synchronized的位置圾叼,只有在一些特殊的場景下蛤克,才能適用volatile∫奈茫總的來說构挤,必須同時滿足下面兩個條件才能保證在并發(fā)環(huán)境的線程安全:

(1)對變量的寫操作不依賴于當(dāng)前值。如i++不符合

(2)該變量沒有包含在具有其他變量的不變式中惕鼓。

6筋现、鎖

6-1、鎖的獲取和釋放 建立的happens-before關(guān)系


6-2箱歧、鎖的釋放和獲取的內(nèi)存語義

MM會把該線程對應(yīng)的本地內(nèi)存置為無效矾飞。從而使得被監(jiān)視器保護(hù)的臨界區(qū)代碼必須從主內(nèi)存中讀取共享變量。鎖釋放與volatile寫有相同的內(nèi)存語義叫胁;鎖獲取與volatile讀有相同的內(nèi)存語義凰慈。

線程A釋放一個鎖汞幢,實質(zhì)上是線程A向接下來將要獲取這個鎖的某個線程發(fā)出了(線程A對共享變量所做修改的)消息驼鹅。

線程B獲取一個鎖,實質(zhì)上是線程B接收了之前某個線程發(fā)出的(在釋放這個鎖之前對共享變量所做修改的)消息森篷。

線程A釋放鎖输钩,隨后線程B獲取這個鎖,這個過程實質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息仲智。

7买乃、java concurrent包的通用化的實現(xiàn)模式

分析concurrent包的源代碼實現(xiàn),會發(fā)現(xiàn)一個通用化的實現(xiàn)模式钓辆。

首先剪验,聲明共享變量為volatile。

然后前联,使用CAS的原子條件更新來實現(xiàn)線程之間的同步功戚。

同時,配合以volatile的讀/寫和CAS所具有的volatile讀和寫的內(nèi)存語義來實現(xiàn)線程之間的通信似嗤。

7啸臀、final

8、雙重檢查和延遲優(yōu)化

上面代碼表面上看起來烁落,似乎兩全其美:在多個線程試圖在同一時間創(chuàng)建對象時乘粒,會通過加鎖來保證只有一個線程能創(chuàng)建對象豌注。在對象創(chuàng)建好之后,執(zhí)行g(shù)etInstance()將不需要獲取鎖灯萍,直接返回已創(chuàng)建好的對象轧铁。

雙重檢查鎖定看起來似乎很完美,但這是一個錯誤的優(yōu)化旦棉!在線程執(zhí)行到第4行代碼讀取到instance不為null時属桦,instance引用的對象有可能還沒有完成初始化。

問題的根源:

前面的雙重檢查鎖定示例代碼的第7行(instance = new Singleton();)創(chuàng)建一個對象他爸。這一行代碼可以分解為如下的三行偽代碼:

memory = allocate();? //1:分配對象的內(nèi)存空間

ctorInstance(memory);? //2:初始化對象

instance = memory;? ? //3:設(shè)置instance指向剛分配的內(nèi)存地址

上面三行偽代碼中的2和3之間聂宾,可能會被重排序(在一些JIT編譯器上,這種重排序是真實發(fā)生的诊笤,詳情見參考文獻(xiàn)1的“Out-of-order writes”部分)系谐。2和3之間重排序之后的執(zhí)行時序如下:

memory = allocate();? //1:分配對象的內(nèi)存空間

instance = memory;? ? //3:設(shè)置instance指向剛分配的內(nèi)存地址

//注意,此時對象還沒有被初始化讨跟!

ctorInstance(memory);? //2:初始化對象

在知曉了問題發(fā)生的根源之后纪他,我們可以想出兩個辦法來實現(xiàn)線程安全的延遲初始化。

1)不允許2和3重排序晾匠。 ? ? ? ? 2)允許2和3重排序茶袒,但不允許其他線程“看到”這個重排序。

當(dāng)聲明對象的引用為volatile后凉馆,“問題的根源”的三行偽代碼中的2和3之間的重排序薪寓,在多線程環(huán)境中將會被禁止。(注意澜共,這個解決方案需要JDK5或更高版本向叉,因為從JDK5開始使用新的JSR-133內(nèi)存模型規(guī)范,這個規(guī)范增強(qiáng)了volatile的語義嗦董。)

另外一種方式:通過內(nèi)部類的加載來實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末母谎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子京革,更是在濱河造成了極大的恐慌奇唤,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匹摇,死亡現(xiàn)場離奇詭異咬扇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)来惧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門冗栗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事隅居∧浦粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵胎源,是天一觀的道長棉钧。 經(jīng)常有香客問我,道長涕蚤,這世上最難降的妖魔是什么宪卿? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮万栅,結(jié)果婚禮上佑钾,老公的妹妹穿的比我還像新娘。我一直安慰自己烦粒,他們只是感情好休溶,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扰她,像睡著了一般兽掰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徒役,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天孽尽,我揣著相機(jī)與錄音,去河邊找鬼忧勿。 笑死杉女,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的狐蜕。 我是一名探鬼主播宠纯,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼层释!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起快集,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤贡羔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后个初,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乖寒,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年院溺,在試婚紗的時候發(fā)現(xiàn)自己被綠了楣嘁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖逐虚,靈堂內(nèi)的尸體忽然破棺而出聋溜,到底是詐尸還是另有隱情,我是刑警寧澤叭爱,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布撮躁,位于F島的核電站,受9級特大地震影響买雾,放射性物質(zhì)發(fā)生泄漏把曼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一漓穿、第九天 我趴在偏房一處隱蔽的房頂上張望嗤军。 院中可真熱鬧,春花似錦晃危、人聲如沸型雳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽纠俭。三九已至,卻和暖如春浪慌,著一層夾襖步出監(jiān)牢的瞬間冤荆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工权纤, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留钓简,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓汹想,卻偏偏與公主長得像外邓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子古掏,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容