內(nèi)存模型概念
高速緩存存在的意義:臨時(shí)變量都存在于內(nèi)存中镰官,當(dāng)CPU執(zhí)行程序時(shí),速度是非陈鸹酰快的泳唠,但是牽扯到和內(nèi)存交互,存取數(shù)據(jù)(耗時(shí))卿操,會(huì)影響程序的執(zhí)行速度警检,因此孙援,必須引入高速緩存害淤。當(dāng)執(zhí)行程序時(shí),會(huì)先從內(nèi)存中讀取值拓售,然后復(fù)制給高速緩存窥摄,之后程序執(zhí)行,并將結(jié)果寫入高速緩存础淤,最后崭放,再將緩存中的值刷新進(jìn)入內(nèi)存哨苛。高速緩存的存在,提高了程序的執(zhí)行速度
引入的問題
多線程程序中币砂,各個(gè)線程都有自己的高速緩存建峭,因此當(dāng)他們讀取內(nèi)存中的同一個(gè)值時(shí)(即共享變量),無法保證讀取進(jìn)緩存和寫入內(nèi)存時(shí)序决摧,即緩存不一致亿蒸,就會(huì)產(chǎn)生錯(cuò)誤
如何解決?
- 總線加lock
- 通過緩存一致性協(xié)議掌桩。其中一個(gè)協(xié)議的思想為:當(dāng)一個(gè)線程修改了值并將其寫入內(nèi)存之后边锁,會(huì)通知其他線程,將其他線程中的緩存置為無效狀態(tài)波岛,當(dāng)其他線程需要讀取該值時(shí)茅坛,必須重新從內(nèi)存中讀取
三個(gè)性質(zhì)
-
原子性
指一個(gè)或者多個(gè)操作,要么全部執(zhí)行并且執(zhí)行過程中不被任何因素打斷则拷,要么就全部都不執(zhí)行 -
可見性
指當(dāng)多個(gè)線程訪問同一個(gè)變量時(shí)贡蓖,一旦其中的一個(gè)線程改變了該變量的值,其他的線程立刻就可以看到修改后的值
個(gè)人認(rèn)為可以簡(jiǎn)單理解為煌茬,當(dāng)一個(gè)線程修改了值后摩梧,會(huì)立即將該值從高速緩存中寫入內(nèi)存,確保其他線程可以正確的讀取到該值 -
有序性
Java程序執(zhí)行時(shí)宣旱,為了提高執(zhí)行效率仅父,會(huì)發(fā)生指令重排。
具體的說浑吟,就是處理器不會(huì)保證代碼會(huì)嚴(yán)格按照語句的先后順序執(zhí)行笙纤,而是會(huì)保證其代碼的執(zhí)行結(jié)果和嚴(yán)格按照順序執(zhí)行語句的結(jié)果一致。
例如:
Person p = new Person()
大致會(huì)做三件事:
- 給Person的實(shí)例分配內(nèi)存空間
- 調(diào)用Person()的構(gòu)造函數(shù)组力,初始化成員字段
- 將person對(duì)象指向分配的內(nèi)存空間(此時(shí)省容,person就不是null了)
但是,由于上述亂序的存在燎字,上述過程的2和3順序是無法保證的
當(dāng)在多線程情況下腥椒,執(zhí)行順序是1-3-2時(shí),當(dāng)執(zhí)行完1-3后候衍,切換至線程B笼蛛,此時(shí)得到的p并未完全初始化, 在使用時(shí)就會(huì)有問題蛉鹿,這也是DLC單例在多線程環(huán)境下必須加volatile修飾instance的原因
注意:要讓程序在多線程情況下正確的執(zhí)行滨砍,那就必須同時(shí)滿足上述三個(gè)條件,若其中一個(gè)不滿足,就可能會(huì)導(dǎo)致運(yùn)行結(jié)果不正確
Java的內(nèi)存模型
Java中規(guī)定惋戏,所有的變量都是存在主存中领追,每一個(gè)線程都有自己的工作內(nèi)存(相當(dāng)于高速緩存),在程序執(zhí)行時(shí)响逢,線程對(duì)變量的所有操作都必須在其工作內(nèi)存中
- 原子性
Java只會(huì)保證所有的基本類型數(shù)據(jù)绒窑,其簡(jiǎn)單的讀取,賦值操作是原子的舔亭,變量之間的賦值不是原子的
若要其他類型數(shù)據(jù)或者操作實(shí)現(xiàn)原子性回论,可以使用sychronized和lock關(guān)鍵字實(shí)現(xiàn)
volatile變量并不能提供原子性 - 可見性
Java提供volatile關(guān)鍵字保證可見性(只針對(duì)共享變量而言)
通過volatile修飾,Java保證對(duì)值的修改會(huì)立刻刷新至主存中分歇,當(dāng)其他線程需要讀取時(shí)(不確定是否一定會(huì)從主存中重新讀瓤亍?)职抡,就會(huì)去讀取新值
同樣的葬燎,也可使用sychronized和lock關(guān)鍵字實(shí)現(xiàn) - 有序性
可以通過volatile關(guān)鍵字實(shí)現(xiàn)部分有序性
可以使用sychronized和lock關(guān)鍵字實(shí)現(xiàn)
volatile
如果一個(gè)域聲明為volatile,那么編譯器和虛擬機(jī)就知道該域可能是被另一個(gè)線程并發(fā)更新的
volatile修飾共享變量的意義:
- 保證對(duì)該域修改的可見性
- 禁止指令進(jìn)行重排缚甩,具體是指:
- 當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí)谱净,在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見擅威;在其后面的操作肯定還沒有進(jìn)行壕探;
- 在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量訪問的語句放在其后面執(zhí)行郊丛,也不能把volatile變量后面的語句放到其前面執(zhí)行
簡(jiǎn)單來說李请,就是volatile會(huì)保證volatile之前的語句一定會(huì)在volatile之后的語句之前執(zhí)行,但是并不能保證前面的語句不會(huì)亂序執(zhí)行厉熟,其后的語句不會(huì)亂序執(zhí)行
即是指:
- 它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置导盅,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí)揍瑟,在它前面的操作已經(jīng)全部完成白翻;
- 它會(huì)強(qiáng)制將對(duì)緩存的修改操作立即寫入主存;
- 如果是寫操作绢片,它會(huì)導(dǎo)致其他CPU中對(duì)應(yīng)的緩存行無效
使用場(chǎng)景:
由于volatile無法保證原子性滤馍,因此,只有在明確可以確保原子性的情況下底循,可以使用volatile巢株,其效率會(huì)高于sychroniezd關(guān)鍵字,例如DCL單例中使用volatile和sychronized確保正確性
假設(shè)對(duì)于共享變量此叠,除了賦值操作之外不會(huì)再有其他操作纯续,那個(gè)可以將其聲明為volatile