Java內(nèi)存模型

前言

Java內(nèi)存模型是Java虛擬機(jī)制定的一種規(guī)范长搀,眾所周知的Java程序都是運(yùn)行在JVM上的,而Java語(yǔ)言“一次編寫(xiě)鸡典,到處運(yùn)行”的特效很多程度是來(lái)源與虛擬機(jī)源请,因?yàn)椴煌牟僮飨到y(tǒng)都有自身相對(duì)應(yīng)的內(nèi)存模型和線(xiàn)程調(diào)度的方式,所以之前一些語(yǔ)言很難解決的問(wèn)題就是同一套代碼在不同平臺(tái)上面運(yùn)行時(shí)會(huì)因?yàn)椴僮飨到y(tǒng)以及機(jī)器的不同彻况,產(chǎn)生很多的問(wèn)題谁尸,而JVM中的內(nèi)存模型規(guī)范就把開(kāi)發(fā)人員和不同的硬件、操作系統(tǒng)分離開(kāi)來(lái)纽甘,使得開(kāi)發(fā)人員更好的專(zhuān)注于業(yè)務(wù)良蛮,除了Java是純粹的面向?qū)ο笳Z(yǔ)言以外,對(duì)各個(gè)平臺(tái)的一致性也是Java如此多的被用于服務(wù)器端非常重要的原因之一悍赢,并發(fā)的程序在之前由于平臺(tái)的差異產(chǎn)生的問(wèn)題尤為突出决瞳,說(shuō)這些,是因?yàn)橐肜斫釰ava并發(fā)編程的一些問(wèn)題左权,對(duì)Java內(nèi)存模型的了解是十分重要的皮胡,現(xiàn)在開(kāi)始進(jìn)入正題。

簡(jiǎn)單介紹

Java內(nèi)存模型主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則涮总,這里不要和Java內(nèi)存區(qū)域搞混了胸囱,內(nèi)存區(qū)域是對(duì)Java程序進(jìn)行的內(nèi)存劃分,這里可以看看我之前寫(xiě)的一篇Java內(nèi)存區(qū)域,而Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則瀑梗,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出時(shí)的具體細(xì)節(jié)烹笔。這里的變量包括了實(shí)例字段裳扯、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素,但不包括局部變量和方法參數(shù)(因?yàn)榫€(xiàn)程私有谤职,不存在資源共享和競(jìng)爭(zhēng)的問(wèn)題)饰豺。Java內(nèi)存模型沒(méi)有限制執(zhí)行引擎使用處理器的特點(diǎn)寄存器或緩存來(lái)和主內(nèi)存進(jìn)行交互,也沒(méi)有限制即使編譯器對(duì)代碼的執(zhí)行順序進(jìn)行調(diào)整允蜈。首先看一張圖

線(xiàn)程冤吨、工作內(nèi)存、主內(nèi)存的關(guān)系

Jvm通過(guò)Java內(nèi)存模型(JMM)協(xié)調(diào)線(xiàn)程饶套、工作內(nèi)存之間的漩蟆。Java內(nèi)存模型規(guī)定了所有的變量都儲(chǔ)存在主內(nèi)存內(nèi),每條線(xiàn)程有自己的工作內(nèi)存妓蛮,線(xiàn)程的工作內(nèi)存中保存了被該線(xiàn)程使用到的變量的主內(nèi)存副本拷貝(注意拷貝的不是對(duì)象本身怠李,一般是對(duì)象的引用、對(duì)象中某個(gè)線(xiàn)程訪問(wèn)到的字段)蛤克。線(xiàn)程對(duì)變量的讀取捺癞、賦值等都必須在工作內(nèi)存中進(jìn)行,不能直接讀取主內(nèi)存中的變量(即使對(duì)于volatile關(guān)鍵字也不例外)构挤。不同的線(xiàn)程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量髓介,線(xiàn)程間變量值的傳遞需要通過(guò)主內(nèi)存來(lái)完成,因?yàn)槌绦虻淖x寫(xiě)時(shí)主要訪問(wèn)的是工作內(nèi)存筋现,所以虛擬機(jī)會(huì)讓工作內(nèi)存優(yōu)先存儲(chǔ)于寄存器和高速緩存中唐础。

內(nèi)存間交互操作

內(nèi)存間交互指一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存的實(shí)現(xiàn)細(xì)節(jié)夫否,Java內(nèi)存模型定義了8大操作來(lái)完成彻犁,虛擬保證了其中每一種操作都是原子的叫胁、不可再分的凰慈,看看下面的圖

八大操作

看看這張圖,要把變量進(jìn)行讀寫(xiě)操作驼鹅,就要經(jīng)過(guò)上圖中的流程微谓,如果進(jìn)行讀取操作,先給這個(gè)變量加鎖(Lock)输钩,然后把這個(gè)變量從主內(nèi)存?zhèn)鬏數(shù)骄€(xiàn)程的工作內(nèi)存中(Read)豺型,接下來(lái)把得到的變量值放入到工作內(nèi)存的變量副本中(Load),這樣就可以給Java線(xiàn)程讀取了(Use)买乃;如果要進(jìn)行寫(xiě)操作姻氨,首先把一個(gè)從執(zhí)行引擎接收到的值賦值給內(nèi)存的變量(Assign),然后把工作內(nèi)存中的一個(gè)變量值傳送到主內(nèi)存中(Store)剪验,最后把通過(guò)Store操作得到的變量值放入主內(nèi)的變量中(Write)肴焊,此后就是釋放鎖(UnLock)前联。這里是說(shuō)按常規(guī)順序,Java內(nèi)存模型只對(duì)Read和Load(進(jìn)行讀操作時(shí))娶眷,Store和Write(進(jìn)行寫(xiě)操作時(shí))操作保證了必須順序執(zhí)行似嗤,如果對(duì)多個(gè)變量進(jìn)行同時(shí)訪問(wèn)時(shí)順序是可以,整體的順序是可以被打亂的届宠,如read a烁落、read b、load b豌注、load a伤塌。除此之外,在執(zhí)行8大操作時(shí)還必須滿(mǎn)足一下的規(guī)則

8大操作遵循的規(guī)則

1.不允許出現(xiàn)一個(gè)變量從主內(nèi)存讀取了但工作內(nèi)存不接受轧铁,或者從工作內(nèi)存讀發(fā)起回寫(xiě)但主內(nèi)存不接受的情況寸谜。
2.變量在工作內(nèi)存中改變了之后必須把該變化同步回主內(nèi)存。
3.不允許一個(gè)線(xiàn)程沒(méi)有發(fā)生過(guò)assign操作就把數(shù)據(jù)從工作內(nèi)存同步回主內(nèi)存属桦。
4.對(duì)一個(gè)變量進(jìn)行use和store之前必須執(zhí)行過(guò)assign和load操作熊痴。
5.一個(gè)變量在同一時(shí)刻只允許一條線(xiàn)程對(duì)其進(jìn)行lock操作,但lock操作可以被同一線(xiàn)程重復(fù)執(zhí)行多次聂宾。
6.對(duì)一個(gè)變量執(zhí)行lock操作之前果善,回清除工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前系谐,需要重新執(zhí)行load和assgin操作初始化變量的值巾陕。
7.如果一個(gè)變量沒(méi)有進(jìn)行鎖定,就不得進(jìn)行unlock操作纪他,不允許unlock被其他線(xiàn)程鎖定的變量鄙煤。
8.對(duì)一個(gè)變量執(zhí)行unlock之前,必須把此變量同步回主內(nèi)存中茶袒。

指令重排序

指令重排序是為了程序獲得更好的性能梯刚,JVM對(duì)程序進(jìn)行的優(yōu)化,普通的變量能保證在方法執(zhí)行過(guò)程中所有依賴(lài)賦值結(jié)果的地方都能獲取到正確的結(jié)果薪寓,而不保證變量賦值的順序與程序代碼中的順序一致亡资,也正是由于這一點(diǎn),所以程序多線(xiàn)程并發(fā)執(zhí)行時(shí)會(huì)產(chǎn)生很多的問(wèn)題向叉。說(shuō)到指令重排序锥腻,就不得不說(shuō)下volatile關(guān)鍵字,volatile保證了變量對(duì)所有線(xiàn)程的可見(jiàn)性母谎,也就是一旦變量值被修改瘦黑,可以直接被其他線(xiàn)程得知,不需要通過(guò)主內(nèi)存來(lái)進(jìn)行傳遞(注意!可見(jiàn)性并不等于原子性)幸斥;volatile還有一個(gè)重要的作用就是禁止指令重排序優(yōu)化存崖,指令重排序雖說(shuō)是為了提高程序的性能,但會(huì)因此帶來(lái)并發(fā)問(wèn)題睡毒,這里用一段代碼來(lái)看看

public class SingletonExample5 {

    private SingletonExample5() {

    }

    // 單例對(duì)象 volatile+雙重校驗(yàn)鎖防止指令重排
    private static volatile SingletonExample5 instance = null;

    /**
     *  線(xiàn)程不安全 多線(xiàn)程模式下JVM和CPU優(yōu)化導(dǎo)致指令重排
     * @return
     */
    public static SingletonExample5 getInstance() {
        if (instance == null) {
            synchronized (SingletonExample5.class) {
                if (instance == null) {
                    instance = new SingletonExample5();
                }
            }
        }
        return instance;
    }
}

對(duì)設(shè)計(jì)模式了解的都知道来惧,這就是使用了雙層校驗(yàn)鎖的單例模式,這里這個(gè)變量就是要用volatile來(lái)進(jìn)行修飾演顾,因?yàn)樵诙嗑€(xiàn)程模式下供搀,判斷instance為空后就把這個(gè)類(lèi)進(jìn)行了鎖定,如果這時(shí)另一個(gè)線(xiàn)程訪問(wèn)到這里钠至,可能會(huì)導(dǎo)致變量instance未被初始化就指向了分配的內(nèi)存空間葛虐,前面說(shuō)過(guò),指令重排導(dǎo)致出現(xiàn)的這個(gè)問(wèn)題棉钧,而使用了volatile關(guān)鍵字后屿脐,會(huì)在變量賦值后插入一道內(nèi)存屏障,指令重排序時(shí)不能把后面的指令重排序到屏障之前的位置宪卿,這樣就保證了變量能夠安全的執(zhí)行后面的操作的诵。而對(duì)于此時(shí)進(jìn)來(lái)的線(xiàn)程來(lái)說(shuō),當(dāng)執(zhí)行引擎執(zhí)行到內(nèi)存屏障這里時(shí)佑钾,意外著之前的操作已經(jīng)完成西疤,這個(gè)線(xiàn)程將放棄后續(xù)的操作。

原子性休溶、可見(jiàn)性代赁、有序性

1.原子性: 由Java內(nèi)存模型來(lái)直接保證的原子性變量操作包括read、load兽掰、assign芭碍、use、store和write孽尽,這些主要是對(duì)變量的讀寫(xiě)操作窖壕,如果需要更大范圍的原子性保證,可以用synchronized關(guān)鍵字泻云。
2.可見(jiàn)性:  指當(dāng)一個(gè)線(xiàn)程修改了共享變量的值艇拍,其它線(xiàn)程能立即得知這個(gè)修改。主要提供的關(guān)鍵字有volatile宠纯、synchronizedfinal层释,volatile就不多說(shuō)了婆瓜,sychronized的可見(jiàn)性是“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把變量同步回主內(nèi)存”;而final關(guān)鍵字是由于被其修飾的字段一旦初始化完成廉白,構(gòu)造器沒(méi)有通過(guò)this的引用傳遞出去个初,其它線(xiàn)程就能直接獲取final的值。
3.有序性: 線(xiàn)程內(nèi)表現(xiàn)為串行猴蹂,但多線(xiàn)程情況下會(huì)發(fā)生指令重排序和工作內(nèi)存與主內(nèi)存同步延遲院溺。

先行發(fā)生原則

先行發(fā)生原則是Java內(nèi)存模型已經(jīng)制定的并且無(wú)需任何其它操作就存在的規(guī)范,這也是判斷數(shù)據(jù)是否存在競(jìng)爭(zhēng)磅轻、線(xiàn)程是否安全的主要依據(jù)珍逸,如下

1.程序次序規(guī)則:在一個(gè)線(xiàn)程內(nèi),按照程序的控制流的順序依次執(zhí)行聋溜。
2.管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作谆膳。
3.volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。
4.線(xiàn)程啟動(dòng)規(guī)則:Thead對(duì)象的start()方法先行發(fā)生于此線(xiàn)程的每一個(gè)動(dòng)作撮躁。
5.線(xiàn)程終止原則:線(xiàn)程中的所有操作都先行發(fā)生于對(duì)此線(xiàn)程的終止檢測(cè)漱病。
6.線(xiàn)程中斷規(guī)則:對(duì)線(xiàn)程interrupt()方法的調(diào)用先行發(fā)生于被中斷線(xiàn)程的代碼檢測(cè)到中斷事件的發(fā)生。
7.對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象初始化的完成先行與它的finalize()方法的開(kāi)始把曼。
8.傳遞性:如果操作A先行發(fā)生于操作B杨帽,操作B先行發(fā)生于操作C,那么操作A先行發(fā)生于操作C嗤军。

結(jié)尾

Java內(nèi)存模型的總結(jié)就到這里了睦尽,大部分是學(xué)習(xí)的筆記,也有自己的一些理解型雳,如果有不對(duì)的地方当凡,請(qǐng)留言指教。只要花時(shí)間去理解纠俭,這些東西也不是那么枯燥的沿量,共勉。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末冤荆,一起剝皮案震驚了整個(gè)濱河市朴则,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钓简,老刑警劉巖乌妒,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異外邓,居然都是意外死亡撤蚊,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)损话,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)侦啸,“玉大人槽唾,你說(shuō)我怎么就攤上這事」馔浚” “怎么了庞萍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)忘闻。 經(jīng)常有香客問(wèn)我钝计,道長(zhǎng),這世上最難降的妖魔是什么齐佳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任私恬,我火速辦了婚禮,結(jié)果婚禮上重虑,老公的妹妹穿的比我還像新娘践付。我一直安慰自己,他們只是感情好缺厉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布永高。 她就那樣靜靜地躺著,像睡著了一般提针。 火紅的嫁衣襯著肌膚如雪命爬。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天辐脖,我揣著相機(jī)與錄音饲宛,去河邊找鬼。 笑死嗜价,一個(gè)胖子當(dāng)著我的面吹牛艇抠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播久锥,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼家淤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瑟由?” 一聲冷哼從身側(cè)響起絮重,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎歹苦,沒(méi)想到半個(gè)月后青伤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡殴瘦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年狠角,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痴施。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡擎厢,死狀恐怖究流,靈堂內(nèi)的尸體忽然破棺而出辣吃,到底是詐尸還是另有隱情动遭,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布神得,位于F島的核電站厘惦,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哩簿。R本人自食惡果不足惜宵蕉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望节榜。 院中可真熱鬧羡玛,春花似錦、人聲如沸宗苍。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)讳窟。三九已至让歼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間丽啡,已是汗流浹背谋右。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留补箍,地道東北人改执。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坑雅,于是被迫代替她去往敵國(guó)和親辈挂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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