前言
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)整允蜈。首先看一張圖
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宠纯、synchronized、final层释,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í)間去理解纠俭,這些東西也不是那么枯燥的沿量,共勉。