JMM內(nèi)存模型

學(xué)號(hào):20021211189? ? ? ?姓名:趙治偉

【嵌牛導(dǎo)讀】JMM(Java內(nèi)存模型)源于物理機(jī)CPU架構(gòu)的內(nèi)存模型,最初用于解決MP(多處理器架構(gòu))系統(tǒng)中的緩存一致性問題提针,而JVM為了屏蔽與各個(gè)硬件平臺(tái)和操作系統(tǒng)對(duì)內(nèi)存訪問機(jī)制的差異化,提出了JMM的概念竣贪。Java內(nèi)存模型是一種虛擬機(jī)規(guī)范伏恐,JMM規(guī)范了Java虛擬機(jī)與計(jì)算機(jī)內(nèi)存時(shí)如何協(xié)同工作的:規(guī)定了一個(gè)線程如何和何時(shí)可以看到由其他線程修改過后的共享變量的值,以及在必須時(shí)如何同步的訪問共享變量老玛。

【嵌牛鼻子】JMM內(nèi)存模型

【嵌牛正文】

Java內(nèi)存模型和操作系統(tǒng)內(nèi)存模型的關(guān)系

內(nèi)存模型和操作系統(tǒng)內(nèi)存模型的關(guān)系

Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則,此處提到的變量只包含實(shí)例對(duì)象钧敞、靜態(tài)對(duì)象和構(gòu)成數(shù)組對(duì)象的元素(堆和方法區(qū)中共享的部分)蜡豹,局部變量和方法參數(shù)是線程私有的,不會(huì)共享溉苛。當(dāng)然不會(huì)存在數(shù)據(jù)競(jìng)爭(zhēng)問題镜廉。

JMM規(guī)定了所有變量存儲(chǔ)在主內(nèi)存(Main Memory)中。每個(gè)線程還有自己的工作內(nèi)存(Working Memory)愚战,線程的工作內(nèi)存中保存了該線程使用到的變量的主內(nèi)存的副本拷貝桨吊,線程對(duì)變量的所有操作(讀取威根、賦值等)都必須在工作內(nèi)存進(jìn)行,而不能直接讀寫主內(nèi)存中的變量(volatile變量仍然有工作內(nèi)存的拷貝视乐,由于它特殊性的操作順序規(guī)定,所以看起來如同直接在主內(nèi)存中讀寫訪問一般)敢茁。不同線程之間也無法直接訪問對(duì)方工作內(nèi)存中的變量佑淀,線程之間值的傳遞都需要通過主內(nèi)存來完成。

對(duì)于JMM與JVM本身的內(nèi)存模型彰檬,參照《深入理解Java虛擬機(jī)》的解釋伸刃,主內(nèi)存、工作內(nèi)存與Java內(nèi)存區(qū)域中的Java堆逢倍、棧捧颅、方法區(qū)等并不是同一個(gè)層次的內(nèi)存劃分。如果兩者一定要勉強(qiáng)對(duì)應(yīng)起來较雕,那從變量碉哑、主內(nèi)存、工作內(nèi)存的定義來看亮蒋,主內(nèi)存對(duì)應(yīng)于Java堆中對(duì)象的實(shí)例數(shù)據(jù)部分扣典,而工作內(nèi)存則對(duì)應(yīng)虛擬機(jī)中的局部變量表,寄存器對(duì)應(yīng)著操作數(shù)棧慎玖。

Java內(nèi)存的抽象結(jié)構(gòu)

Java 線程之間的通信由JMM控制贮尖,JMM決定一個(gè)線程對(duì)共享變量寫入何時(shí)對(duì)另一個(gè)線程可見。從抽象的角度來看趁怔,JMM定義了線程和主內(nèi)存之間的抽象關(guān)系:線程之間的共享變量存儲(chǔ)在主內(nèi)存(Main Memory)中湿硝,每個(gè)線程都有一個(gè)私有的本地內(nèi)存(Local Memory),本地內(nèi)存中存儲(chǔ)了該線程共享變量的副本润努。本地內(nèi)存是一個(gè)抽象概念关斜,它涵蓋了緩存、寫緩沖區(qū)任连、寄存器以及其他的硬件和編譯器優(yōu)化蚤吹。Java內(nèi)存模型的抽象示意如圖所示:

內(nèi)存模型示意圖

線程A與線程B之間要通信的話,必須要經(jīng)歷兩個(gè)步驟随抠。

1.線程A把本地內(nèi)存中更新過的共享變量刷新到主內(nèi)存中去裁着。

2.線程B到主內(nèi)存中區(qū)讀取線程A之前已經(jīng)更新過的共享變量。

4.指令重排

計(jì)算機(jī)在執(zhí)行程序時(shí)拱她,為了提高性能二驰,編譯器和處理器常常會(huì)對(duì)指令做重排,一般分為以下3種

編譯器優(yōu)化的重排:編譯器在不改變單線程程序語義的前提下秉沼,可以重新安排語句的執(zhí)行順序桶雀。

指令并行的重排:處理器采用指令級(jí)并行技術(shù)來將多條指令并行執(zhí)行矿酵,如果不存在后一個(gè)執(zhí)行語句依賴前面執(zhí)行語句的情況,處理器可以改變語句對(duì)應(yīng)的機(jī)器指令的執(zhí)行順序矗积。

內(nèi)存系統(tǒng)的重排:由于處理器使用緩存和讀寫緩沖區(qū)全肮,這使得加載和存儲(chǔ)操作看上去是亂序執(zhí)行的。因?yàn)槿?jí)緩存的存在棘捣,導(dǎo)致內(nèi)存與緩存的數(shù)據(jù)同步存在時(shí)間差辜腺。

其中編譯器的重排優(yōu)化屬于編譯期重排,指令并行的重排和內(nèi)存系統(tǒng)的重排屬于處理器重排乍恐,這些重排優(yōu)化可能會(huì)導(dǎo)致程序出現(xiàn)內(nèi)存可見性問題评疗。

JMM內(nèi)存模型作用

JMM是圍繞著程序執(zhí)行的原子性、有序性茵烈、可見性展開的百匆,它是如何保證這三種特性的呢?

我們先了解下這三種特性呜投。

1.原子性

在Java中加匈,java內(nèi)存模型只保證對(duì)基本數(shù)據(jù)類型變量的讀取和賦值操作是原子性操作(long、double)除外宙彪,即這些操作是不可被中斷的矩动,要么全部執(zhí)行,要么不執(zhí)行释漆。例如下面代碼

x = 10;? y = x;x++;x = x + 1;

只有x= 10;是原子性操作悲没,其他三個(gè)語句都不是原子性操作。

x = 10;是直接將數(shù)值10賦給x男图,也就是說線程執(zhí)行這個(gè)語句會(huì)直接將數(shù)值10寫入到工作內(nèi)存中示姿。

y = x ;包含兩個(gè)操作,它先要去讀取x的值逊笆,再將x的值賦值給y寫入工作內(nèi)存栈戳。雖然讀取x的值以及將y的值寫入工作內(nèi)存這兩個(gè)操作都是原子性操作狰闪,但是合起來就不是原子性操作了熬尺。

同樣的 x++和x = x+1;包括三個(gè)操作忆蚀,讀取x的值程储,進(jìn)行加1操作,寫入新的值严就。

所以念颈,上面4個(gè)語句中有x = 10搀军;的操作具備原子性症虑。也就是說缩歪,只有簡單的讀取、賦值才是原子操作谍憔。(而且必須是將基礎(chǔ)類型常量賦值給某個(gè)變量匪蝙,變量之間的相互賦值不是原子操作)

2.可見性

每個(gè)線程會(huì)在自己的工作內(nèi)存來操作共享變量主籍,操作完成之后將工作內(nèi)存中的副本寫回主內(nèi)存,并且在其他線程從主內(nèi)存將變量同步回自己的工作內(nèi)存之前逛球,共享變量的改變對(duì)其他線程是不可見的千元。

當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改后的值需忿,JMM通過在變量修改后將新值同步回主內(nèi)存诅炉,其他線程在變量讀取前從主內(nèi)存讀取新值刷新到工作內(nèi)存。這種依賴主內(nèi)存作為傳遞媒介的方法來實(shí)現(xiàn)可見性屋厘。

volatile之可見性

當(dāng)一個(gè)共享變量被volatile修飾時(shí),它會(huì)保證當(dāng)前線程修改的值立即被更新到主存月而。

其他線程工作內(nèi)存中的變量會(huì)強(qiáng)制立即失效汗洒,當(dāng)其他線程需要讀取時(shí),回去主內(nèi)存中讀取最新值父款。

而普通的共享變量不能保證可見性溢谤,因?yàn)槠胀ü蚕碜兞勘恍薷暮螅裁磿r(shí)候被寫入主存是不確定的憨攒,當(dāng)其他線程去讀取時(shí)世杀,此時(shí)內(nèi)存中可能還是原來的舊值,因此無法保證可見性肝集。

3.有序性

在Java內(nèi)存模型中瞻坝,允許編譯器和處理器對(duì)指令進(jìn)行重排序,重排序過程不會(huì)影響到單線程的執(zhí)行杏瞻,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性所刀。

例子:指令重排導(dǎo)致DCL失效

publicclassSingleton{// 指向自己實(shí)例的私有靜態(tài)引用privatestaticSingletoninstance=null;// 私有的構(gòu)造方法privateSingleton(){}// 以自己實(shí)例為返回值的靜態(tài)的公有方法,靜態(tài)工廠方法publicstaticSingletongetInstance(){// 被動(dòng)創(chuàng)建捞挥,在真正需要使用時(shí)才去創(chuàng)建if(instance==null){//同一時(shí)刻只有一個(gè)線程進(jìn)入同步塊執(zhí)行創(chuàng)建對(duì)象synchronzied(Singleton.class){if(instance==null){instance=newSingleton();}}}returninstance;}}

看似簡單的一行賦值語句:instance = new Singleton();其實(shí)內(nèi)部已經(jīng)轉(zhuǎn)為多條指令浮创。

memory=allocate();//分配內(nèi)存ctorInstance(memory);//初始化對(duì)象instance=memory;//設(shè)置instance引用指向剛才分配的內(nèi)存地址

但是有可能經(jīng)過指令重排后如下

memory=allocate();//分配內(nèi)存instance=memory;//設(shè)置instance引用指向剛才分配的內(nèi)存地址ctorInstance(memory);//初始化對(duì)象

可以看到指令重排后,instance指向分配好的內(nèi)存放在了前面砌函,在線程A初始化完成這段內(nèi)存之前斩披,線程B雖然進(jìn)不去同步代碼塊,但是在同步代碼塊之前的判斷就會(huì)發(fā)現(xiàn)instance不為空讹俊,此時(shí)線程B獲得一個(gè)不完整(未初始化)的 Singleton 對(duì)象進(jìn)行使用就可能發(fā)生錯(cuò)誤垦沉。

volatile之有序性

原理:volatile的可見性和有序性都是通過加入內(nèi)存屏障來實(shí)現(xiàn)。

會(huì)在寫之后加入一條store屏障指令劣像,將本地內(nèi)存中值刷新到主內(nèi)存乡话。

會(huì)在讀之前加入一條load屏障指令,從主內(nèi)存中讀取共享變量耳奕。

synchronized 之有序性

JMM中有一條關(guān)于synchronized的規(guī)則:一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock绑青。這條規(guī)則決定了同步塊中的操作相當(dāng)于單線程執(zhí)行诬像。

as if serial :不管怎么重排序,單線程的執(zhí)行結(jié)果不能被改變闸婴。編譯器和處理器都必須遵守as if serial語義坏挠。也就是說上面的重排序不會(huì)影響到單線程程序的執(zhí)行,所以堆外就表現(xiàn)出了有序性邪乍。

JMM提供的解決方案

在理解了原子性降狠、可見性、順序性后庇楞,我們看看JMM如何保證這三種特性榜配。

1.除了JVM自身提供的對(duì)基本數(shù)據(jù)類型讀寫操作的原子性外,對(duì)于方法級(jí)別或者代碼塊級(jí)別的原子性操作吕晌,可以使用synchronized 或者lock接口蛋褥,保證程序執(zhí)行的原子性。

2.而工作內(nèi)存與主內(nèi)存同步延遲導(dǎo)致的可見性問題睛驳,可以使用synchronized 或者volitile 關(guān)鍵字解決烙心,他們都可以使一個(gè)線程修改后的變量對(duì)其他線程立即可見。

3.對(duì)于指令重排導(dǎo)致的有序性問題乏沸,可以使用volitile 或者synchronized關(guān)鍵字解決淫茵,volatile另外一個(gè)作用是禁止指令重排,synchronized會(huì)變成單線程操作 as if serial保證了結(jié)果的一致性蹬跃。

4.除了靠synchronized 和volatile 關(guān)鍵字來保證有序性匙瘪、原子性、可見性外炬转,JMM內(nèi)部還定義了一套happens-before原則來保證多線程環(huán)境下兩個(gè)操作的原子性辆苔、可見性、順序性扼劈。

happens-before

倘若在開發(fā)中驻啤,僅靠synchronized和volatile來保證順序性、原子性荐吵、可見性骑冗。那么編寫并發(fā)程序會(huì)十分麻煩。在Java內(nèi)存模型中先煎,還提供了happens-before原則來輔助保證程序執(zhí)行的原子性贼涩、可見性、有序性的問題薯蝎。它是判斷數(shù)據(jù)是否存在競(jìng)爭(zhēng)遥倦,線程是否安全的依據(jù)。happens-before原則內(nèi)容如下:

程序順序原則占锯,即在一個(gè)線程內(nèi)必須保證語義串行性袒哥,也就是說按照代碼順序執(zhí)行缩筛。

鎖規(guī)則 解鎖(unlock)操作必然發(fā)生在后續(xù)的同一個(gè)鎖的加鎖(lock)之前,也就是說堡称,如果對(duì)于一個(gè)鎖解鎖后瞎抛,再加鎖,那么加鎖的動(dòng)作必須在解鎖動(dòng)作之后(同一個(gè)鎖)却紧。

volatile規(guī)則 volatile變量的寫桐臊,先發(fā)生于讀,這保證了volatile變量的可見性晓殊,簡單的理解就是断凶,volatile變量在每次被線程訪問時(shí),都強(qiáng)迫從主內(nèi)存中讀該變量的值巫俺,而當(dāng)該變量發(fā)生變化時(shí)懒浮,又會(huì)強(qiáng)迫將最新的值刷新到主內(nèi)存,任何時(shí)刻识藤,不同的線程總是能夠看到該變量的最新值。

線程啟動(dòng)規(guī)則 線程的start()方法先于它的每一個(gè)動(dòng)作次伶,即如果線程A在執(zhí)行線程B的start方法之前修改了共享變量的值痴昧,那么當(dāng)線程B執(zhí)行start方法時(shí),線程A對(duì)共享變量的修改對(duì)線程B可見

傳遞性 A先于B 冠王,B先于C 那么A必然先于C

線程終止規(guī)則 線程的所有操作先于線程的終結(jié)赶撰,Thread.join()方法的作用是等待當(dāng)前執(zhí)行的線程終止。假設(shè)在線程B終止之前柱彻,修改了共享變量豪娜,線程A從線程B的join方法成功返回后,線程B對(duì)共享變量的修改將對(duì)線程A可見哟楷。

線程中斷規(guī)則 對(duì)線程 interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生瘤载,可以通過Thread.interrupted()方法檢測(cè)線程是否中斷。

對(duì)象終結(jié)規(guī)則 對(duì)象的構(gòu)造函數(shù)執(zhí)行卖擅,結(jié)束先于finalize()方法

上述8條原則無需手動(dòng)添加任何同步手段(synchronized|volatile)即可達(dá)到效果鸣奔。

volatile 內(nèi)存語義

保證被volatile修飾的共享變量對(duì)所有線程總是可見的。也就是當(dāng)一個(gè)線程修改了一個(gè)被volatile修飾的共享變量的值惩阶,新值總是可以被其他線程立即得知挎狸。

禁止指令重排優(yōu)化。

1.可見性

當(dāng)寫一個(gè)volatile變量時(shí)断楷,JMM會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存中的共享變量值刷新到主內(nèi)存中

當(dāng)讀取一個(gè)volatile變量锨匆,JMM會(huì)把該線程對(duì)應(yīng)的工作內(nèi)存置為無效,那么該線程將只能從主內(nèi)存中重新讀取共享變量冬筒。volatile變量正是通過這種寫-讀的方式實(shí)現(xiàn)對(duì)其他線程可見恐锣。(內(nèi)存語義實(shí)現(xiàn)是通過內(nèi)存屏障)

2.禁止重排優(yōu)化

volatile關(guān)鍵字另一個(gè)作用就是禁止指令重排優(yōu)化茅主,從而避免多線程環(huán)境下程序出現(xiàn)亂序的執(zhí)行現(xiàn)象。

內(nèi)存屏障(Memory Barrier)又稱內(nèi)存柵欄侥蒙,是一個(gè)CPU命令暗膜,它的作用有兩個(gè):

1.保證特定操作的執(zhí)行順序。

2.保證某些變量的內(nèi)存可見性鞭衩。

由于編譯器和處理器都能執(zhí)行指令重排優(yōu)化学搜,如果在指令間插入一條Memory Barrier則會(huì)告訴編譯器和CPU,不管什么指令都不能和這條內(nèi)存屏障指令重排序论衍,Memory Barrier的另外一個(gè)作用是強(qiáng)制刷出各種CPU的緩存數(shù)據(jù)瑞佩。

總之,volatile變量正是通過內(nèi)存屏障實(shí)現(xiàn)其在內(nèi)存中的語義坯台,即可見性和禁止重排優(yōu)化炬丸。

總結(jié)

JMM就是一組規(guī)則,這組規(guī)則解決并發(fā)編程可能出現(xiàn)的線程安全問題蜒蕾。并提供了內(nèi)置解決方案(happens-before原則)及外部可使用的同步手段(synchronized和volatile)稠炬,確保程序在多線程環(huán)境下的原子性、可見性咪啡、順序性首启。

作者:hadoop_a9bb

鏈接:http://www.reibang.com/p/e034762c85cb

來源:簡書

著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán)撤摸,非商業(yè)轉(zhuǎn)載請(qǐng)注明出處毅桃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市准夷,隨后出現(xiàn)的幾起案子钥飞,更是在濱河造成了極大的恐慌,老刑警劉巖衫嵌,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件读宙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渐扮,警方通過查閱死者的電腦和手機(jī)论悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來墓律,“玉大人膀估,你說我怎么就攤上這事〕芊恚” “怎么了察纯?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我饼记,道長香伴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任具则,我火速辦了婚禮即纲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘博肋。我一直安慰自己低斋,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布匪凡。 她就那樣靜靜地躺著膊畴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪病游。 梳的紋絲不亂的頭發(fā)上唇跨,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音衬衬,去河邊找鬼买猖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滋尉,可吹牛的內(nèi)容都是我干的政勃。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼兼砖,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了既棺?” 一聲冷哼從身側(cè)響起讽挟,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎丸冕,沒想到半個(gè)月后耽梅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胖烛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年眼姐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佩番。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡众旗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趟畏,到底是詐尸還是另有隱情贡歧,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站利朵,受9級(jí)特大地震影響律想,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绍弟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一技即、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧樟遣,春花似錦而叼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屿衅,卻和暖如春埃难,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涤久。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國打工涡尘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人响迂。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓考抄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蔗彤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子川梅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355