》》》》》》博客地址《《《《《《
》》》》》》首發(fā)博客《《《《《《
前言
首先我們在了解java內(nèi)存模型之前先看一下計算機(jī)內(nèi)存模型徘键,理解了計算機(jī)內(nèi)存模型的話后面在看JMM就會簡單的多,上篇文章我是直接寫的。
計算機(jī)內(nèi)存
計算機(jī)是由CPU、主存尾抑、磁盤等組成的(簡單引出問題熬)我們都知道計算機(jī)執(zhí)行程序的指令都是由CPU來執(zhí)行的调炬,執(zhí)行的時候是要處理數(shù)據(jù)的,這些數(shù)據(jù)通常存儲在主存中须蜗。
如圖所示,這時候問題來了,CPU的執(zhí)行速度越來越快明肮,然后內(nèi)存倒是沒什么進(jìn)展菱农,這樣的話CPU的讀寫操作就會非常耗時,效率不就很低了柿估?
所以這個時候就出現(xiàn)了高速緩存(Cache)來解決這個問題循未,那么緩存是什么呢?緩存其實(shí)就是保存的數(shù)據(jù)備存秫舌,特點(diǎn)是快的妖。所以這個時候程序的執(zhí)行過程就變成了這個樣子:首先在運(yùn)行的時候會把數(shù)據(jù)從主存中賦值一份放在緩存中,然后CPU在運(yùn)算的時候就直接去緩存中讀寫數(shù)據(jù)足陨,等執(zhí)行結(jié)束后在把數(shù)據(jù)刷新到主存中嫂粟。這樣一來就大大的提高了執(zhí)行的速度。我們來看一下流程圖:
可以看出墨缘,運(yùn)行的時候L1緩存先把數(shù)據(jù)從主存中讀取出來星虹,然后CPU操作的數(shù)據(jù)是從緩存中讀取,當(dāng)數(shù)據(jù)執(zhí)行完畢镊讼,在從緩存中刷新到主存中宽涌。隨著CPU的執(zhí)行能力越來越強(qiáng),一層緩存已經(jīng)滿足不了需求了蝶棋,這時候就出現(xiàn)了2級緩存(L2Cache)3級緩存(L3Cache),每級緩存都存儲的是下一級緩存的一部分?jǐn)?shù)據(jù)卸亮。
那么當(dāng)CPU需要數(shù)據(jù)的時候就會這樣執(zhí)行:首先去一級緩存(L1Cache)查找,如果一級緩存沒有就去二級緩存(L2Cache)查找,二級緩存沒有就去三級緩存(L3Cache)查找,如果緩存中沒有,就去主存中查找玩裙。 那么問題來了嫡良。
緩存一致性
現(xiàn)代計算機(jī)已經(jīng)不是單個CPU,有多個CPU每個CPU還可能會有多核,單核CPU只有一套緩存分別就是上面所說的L1献酗、L2寝受、L3如圖所示:
如果CPU有多個核心的話,就是每個核心都有L1緩存或者有L2緩存罕偎,而共享L3緩存或者L2緩存很澄。
我們來看一下結(jié)構(gòu)圖:
這個時候每個核心都有自己的高速緩存,它們又共享同一主存颜及,就會造成緩存一致性的問題甩苛,在多線程同時訪問同一共享數(shù)據(jù)的情況下,每個線程都是操作自己緩存的數(shù)據(jù)副本俏站,這個時候就會出現(xiàn)每個緩存中的共享數(shù)據(jù)存在不一致的情況讯蒲。多個處理器運(yùn)算任務(wù)都涉及同一塊主存,需要一種協(xié)議可以保障數(shù)據(jù)的一致性肄扎,這類協(xié)議有MSI墨林、MESI赁酝、MOSI及Dragon Protocol等。
處理器優(yōu)化
上面了解到提高CPU的效率就是在CPU和主存直接增加高速緩存旭等,增加高速緩存會造成緩存不一致的問題酌呆,除了緩存不一致的問題,還有一種問題就是為了能讓處理器內(nèi)部的運(yùn)算單元能夠盡量的被充分利用處理器可能會對輸入代碼進(jìn)行亂序執(zhí)行搔耕,并且處理器會在計算之后將亂序的代碼進(jìn)行結(jié)果重組來保證結(jié)果的一致性隙袁。在Java虛擬機(jī)中也有類似的指令重排序。
思考
這篇文章其實(shí)是講述java內(nèi)存模型的弃榨,為什么會和計算機(jī)硬件扯上關(guān)系呢菩收?注意到上面有說到多線程的情況下會造成緩存不一致的問題,提到多線程就離不開并發(fā)鲸睛,想到并發(fā)的話就離不開三大問題娜饵,可見性巧勤,原子性瓦呼,有序性的問題。那這三種特性不就是上面所說到的緩存不一致,處理器優(yōu)化和指令重排序問題嗎钧萍。這這樣看來緩存不一致不就是可見性的問題,而原子性不就是處理器優(yōu)化所導(dǎo)致的原子性問題政鼠,指令重排序就是導(dǎo)致有序性的問題风瘦。那么Java內(nèi)存模型又是什么呢?
java內(nèi)存模型
Java內(nèi)存模型的作用就是用來屏蔽掉不同操作系統(tǒng)中的內(nèi)存差異性來保持并發(fā)的一致性公般。同時JMM也規(guī)范了JVM如何與計算機(jī)內(nèi)存進(jìn)行交互万搔。簡單的來說java內(nèi)存模型就是Java自己的一套協(xié)議來屏蔽掉各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,實(shí)現(xiàn)平臺一致性達(dá)到最終的"一次編寫官帘,到處運(yùn)行"瞬雹。看到這里就知道了Jmm是用來做什么的刽虹。同時Java內(nèi)存模型可以理解為java并發(fā)內(nèi)存模型酗捌。然后JMM
通信
Java內(nèi)存模型(以下簡稱JMM)規(guī)定了,所有變量都存儲在主內(nèi)存中涌哲,每個線程都有自己的本地緩存胖缤,所以線程中對變量的操作都必須在本地緩存中進(jìn)行并不是直接操作主內(nèi)存,線程之間的無法訪問對方線程的變量阀圾,想要通信的話就只能通過主內(nèi)存進(jìn)行通信哪廓。
JMM抽象示意圖:
從上圖可以看出每個線程都有一個本地內(nèi)存,如果線程想要通信的話要執(zhí)行一下步驟:
- A線程先把本地內(nèi)存的值寫入主內(nèi)存
- B線程從主內(nèi)存中去讀取出A線程寫的值
具體通信規(guī)則可以參考我上一篇文章:Java內(nèi)存模型里面定義了八種通信規(guī)則初烘。
到這里就對JMM有個清晰的理解了涡真。JMM其實(shí)是一種規(guī)范分俯,其主要目的就是為了解決多線程通過共享內(nèi)存進(jìn)行通信時所產(chǎn)生的本地內(nèi)存數(shù)據(jù)不一致,編譯器會對代碼指令重排序综膀、處理器會對代碼亂序執(zhí)行等帶來的問題澳迫。
解決的問題
JMM所解決的問題離不開我們上面所說的三大特性:可見性、原子性剧劝、有序性.
原子性:在java中使用synchronized關(guān)鍵字保證代碼的原子性橄登,synchronized實(shí)現(xiàn)原理后面會單獨(dú)寫一篇文章。
可見性:volatile關(guān)鍵字保證了多線程操控變量的可見性讥此,同時synchronized和final也可以保證變量的可見性拢锹,注意:volatile并不保證原子性,所以什么時候用volatile一定要注意萄喳。
有序性:volatile可以禁用指令重排卒稳,synchronized關(guān)鍵字保證同一時刻只允許一條線程操作所以我們可以發(fā)現(xiàn)synchronized可以解決三種問題,所以使用synchronized關(guān)鍵字比較多他巨,但是synchronized只允許一個線程進(jìn)行操作充坑,會造成上下文切換的效率問題。
總結(jié)
通過上文一定對JMM是什么染突,和有什么作用有了一定的理解這里推薦《深入理解Java虛擬機(jī)》