併發之初章Java記憶體模型
》》》》》》部落格地址《《《《《《
》》》》》》首發部落格《《《《《《
前言
首先我們在瞭解java記憶體模型之前先看一下計算機記憶體模型,理解了計算機記憶體模型的話後面在看JMM就會簡單的多,上篇文章我是直接寫的。
計算機記憶體
計算機是由CPU、主存、磁碟等組成的(簡單引出問題熬)我們都知道計算機執行程式的指令都是由CPU來執行的,執行的時候是要處理資料的,這些資料通常儲存在主存中。
如圖所示,這時候問題來了,CPU的執行速度越來越快,然後記憶體倒是沒什麼進展,這樣的話CPU的讀寫操作就會非常耗時,效率不就很低了?
所以這個時候就出現了快取記憶體(Cache)來解決這個問題,那麼快取是什麼呢?快取其實就是儲存的資料備存,特點是快。所以這個時候程式的執行過程就變成了這個樣子:首先在執行的時候會把資料從主存中賦值一份放在快取中,然後CPU在運算的時候就直接去快取中讀寫資料,等執行結束後在把資料重新整理到主存中。這樣一來就大大的提高了執行的速度。我們來看一下流程圖:
可以看出,執行的時候L1快取先把資料從主存中讀取出來,然後CPU操作的資料是從快取中讀取,當資料執行完畢,在從快取中重新整理到主存中。隨著CPU的執行能力越來越強,一層快取已經滿足不了需求了,這時候就出現了2級快取(L2Cache)3級快取(L3Cache),每級快取都儲存的是下一級快取的一部分資料。
那麼當CPU需要資料的時候就會這樣執行:首先去一級快取(L1Cache)查詢,如果一級快取沒有就去二級快取(L2Cache)查詢,二級快取沒有就去三級快取(L3Cache)查詢,如果快取中沒有,就去主存中查詢。 那麼問題來了。
快取一致性
現代計算機已經不是單個CPU,有多個CPU每個CPU還可能會有多核,單核CPU只有一套快取分別就是上面所說的L1、L2、L3如圖所示:
如果CPU有多個核心的話,就是每個核心都有L1快取或者有L2快取,而共享L3快取或者L2快取。
我們來看一下結構圖:
這個時候每個核心都有自己的快取記憶體,它們又共享同一主存,就會造成快取一致性的問題,在多執行緒同時訪問同一共享資料的情況下,每個執行緒都是操作自己快取的資料副本,這個時候就會出現每個快取中的共享資料存在不一致的情況。多個處理器運算任務都涉及同一塊主存,需要一種協議可以保障資料的一致性,這類協議有MSI、MESI、MOSI及Dragon Protocol等。
處理器優化
上面瞭解到提高CPU的效率就是在CPU和主存直接增加快取記憶體,增加快取記憶體會造成快取不一致的問題,除了快取不一致的問題,還有一種問題就是為了能讓處理器內部的運算單元能夠儘量的被充分利用處理器可能會對輸入程式碼進行亂序執行,並且處理器會在計算之後將亂序的程式碼進行結果重組來保證結果的一致性。在Java虛擬機器中也有類似的指令重排序。
思考
這篇文章其實是講述java記憶體模型的,為什麼會和計算機硬體扯上關係呢?注意到上面有說到多執行緒的情況下會造成快取不一致的問題,提到多執行緒就離不開併發,想到併發的話就離不開三大問題,可見性,原子性,有序性的問題。那這三種特性不就是上面所說到的快取不一致,處理器優化和指令重排序問題嗎。這這樣看來快取不一致不就是可見性的問題,而原子性不就是處理器優化所導致的原子性問題,指令重排序就是導致有序性的問題。那麼Java記憶體模型又是什麼呢?
java記憶體模型
Java記憶體模型的作用就是用來遮蔽掉不同作業系統中的記憶體差異性來保持併發的一致性。同時JMM也規範了JVM如何與計算機記憶體進行互動。簡單的來說java記憶體模型就是Java自己的一套協議來遮蔽掉各種硬體和作業系統的記憶體訪問差異,實現平臺一致性達到最終的"一次編寫,到處執行"。看到這裡就知道了Jmm是用來做什麼的。同時Java記憶體模型可以理解為java併發記憶體模型。然後JMM
通訊
Java記憶體模型(以下簡稱JMM)規定了,所有變數都儲存在主記憶體中,每個執行緒都有自己的本地快取,所以執行緒中對變數的操作都必須在本地快取中進行並不是直接操作主記憶體,執行緒之間的無法訪問對方執行緒的變數,想要通訊的話就只能通過主記憶體進行通訊。
JMM抽象示意圖:
從上圖可以看出每個執行緒都有一個本地記憶體,如果執行緒想要通訊的話要執行一下步驟:
- A執行緒先把本地記憶體的值寫入主記憶體
- B執行緒從主記憶體中去讀取出A執行緒寫的值
具體通訊規則可以參考我上一篇文章:Java記憶體模型裡面定義了八種通訊規則。
到這裡就對JMM有個清晰的理解了。JMM其實是一種規範,其主要目的就是為了解決多執行緒通過共享記憶體進行通訊時所產生的本地記憶體資料不一致,編譯器會對程式碼指令重排序、處理器會對程式碼亂序執行等帶來的問題。
解決的問題
JMM所解決的問題離不開我們上面所說的三大特性:可見性、原子性、有序性.
原子性:在java中使用synchronized關鍵字保證程式碼的原子性,synchronized實現原理後面會單獨寫一篇文章。
可見性:volatile關鍵字保證了多執行緒操控變數的可見性,同時synchronized和final也可以保證變數的可見性,注意:volatile並不保證原子性,所以什麼時候用volatile一定要注意。
有序性:volatile可以禁用指令重排,synchronized關鍵字保證同一時刻只允許一條執行緒操作所以我們可以發現synchronized可以解決三種問題,所以使用synchronized關鍵字比較多,但是synchronized只允許一個執行緒進行操作,會造成上下文切換的效率問題。
總結
通過上文一定對JMM是什麼,和有什麼作用有了一定的理解這裡推薦《深入理解Java虛擬機器