在再有人問你Java內存模型是什麼,就把這篇文章發給他。這篇文章中,咱們介紹過關於Java內容模型的前因後果。程序員
咱們在文章中提到過,因爲CPU和主存的處理速度上存在必定差異,爲了匹配這種差距,提高計算機能力,人們在CPU和主存之間增長了多層高速緩存。每一個CPU會有L一、L2甚至L3緩存,在多核計算機中會有多個CPU,那麼就會存在多套緩存,那麼這多套緩存之間的數據就可能出現不一致的現象。爲了解決這個問題,有了內存模型。內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。經過這些規則來規範對內存的讀寫操做,從而保證指令執行的正確性。面試
不知道小夥伴們有沒有想過這樣的問題:內存模型究竟是怎麼保證緩存一致性的呢?編程
接下來咱們試着回答這個問題。首先,緩存一致性是因爲引入緩存而致使的問題,因此,這是不少CPU廠商必須解決的問題。爲了解決前面提到的緩存數據不一致的問題,人們提出過不少方案,一般來講有如下2種方案:緩存
在早期的CPU當中,是經過在總線上加LOCK#鎖的形式來解決緩存不一致的問題。由於CPU和其餘部件進行通訊都是經過總線來進行的,若是對總線加LOCK#鎖的話,也就是說阻塞了其餘CPU對其餘部件訪問(如內存),從而使得只能有一個CPU能使用這個變量的內存。在總線上發出了LCOK#鎖的信號,那麼只有等待這段代碼徹底執行完畢以後,其餘CPU才能從其內存讀取變量,而後進行相應的操做。這樣就解決了緩存不一致的問題。多線程
可是因爲在鎖住總線期間,其餘CPU沒法訪問內存,會致使效率低下。所以出現了第二種解決方案,經過緩存一致性協議來解決緩存一致性問題。架構
緩存一致性協議(Cache Coherence Protocol),最出名的就是Intel 的MESI協議,MESI協議保證了每一個緩存中使用的共享變量的副本是一致的。併發
MESI的核心的思想是:當CPU寫數據時,若是發現操做的變量是共享變量,即在其餘CPU中也存在該變量的副本,會發出信號通知其餘CPU將該變量的緩存行置爲無效狀態,所以當其餘CPU須要讀取這個變量時,發現本身緩存中緩存該變量的緩存行是無效的,那麼它就會從內存從新讀取。異步
在MESI協議中,每一個緩存可能有有4個狀態,它們分別是:編程語言
關於MESI的更多細節這裏就不詳細介紹了,讀者只要知道,MESI是一種比較經常使用的緩存一致性協議,他能夠用來解決緩存之間的數據一致性問題就能夠了。學習
可是,值得注意的是,傳統的MESI協議中有兩個行爲的執行成本比較大。
一個是將某個Cache Line標記爲Invalid狀態,另外一個是當某Cache Line當前狀態爲Invalid時寫入新的數據。因此CPU經過Store Buffer和Invalidate Queue組件來下降這類操做的延時。
如圖:
當一個CPU進行寫入時,首先會給其它CPU發送Invalid消息,而後把當前寫入的數據寫入到Store Buffer中。而後異步在某個時刻真正的寫入到Cache中。
當前CPU核若是要讀Cache中的數據,須要先掃描Store Buffer以後再讀取Cache。
可是此時其它CPU核是看不到當前核的Store Buffer中的數據的,要等到Store Buffer中的數據被刷到了Cache以後纔會觸發失效操做。
而當一個CPU覈收到Invalid消息時,會把消息寫入自身的Invalidate Queue中,隨後異步將其設爲Invalid狀態。
和Store Buffer不一樣的是,當前CPU核心使用Cache時並不掃描Invalidate Queue部分,因此可能會有極短期的髒讀問題。
因此,爲了解決緩存的一致性問題,比較典型的方案是MESI緩存一致性協議。
MESI協議,能夠保證緩存的一致性,可是沒法保證明時性。
前面介紹過了緩存一致性模型,接着咱們再來看一下內存模型。咱們說過內存模型定義一系列規範,來保證多線程訪問共享變量時的可見性、有序性和原子性。(更多內容請參考再有人問你Java內存模型是什麼,就把這篇文章發給他。)
內存模型(Memory Model)若是擴展開來講的話,一般指的是內存一致性模型(Memory Sequential Consistency Model)
前面咱們提到過緩存一致性,這裏又要說內存一致性,不是故意要把讀者搞蒙,而是但願經過對比讓讀者更加清楚。
緩存一致性(Cache Coherence),解決是多個緩存副本之間的數據的一致性問題。
內存一致性(Memory Consistency),保證的是多線程程序訪問內存時能夠讀到什麼值。
咱們首先看如下程序:
其中,S一、S二、L一、L2是語句代號(S表示Store,L表示Load);r1和r2是兩個寄存器。x和y是兩個不一樣的內存變量。兩個線程執行完以後,r1和r2多是什麼值?
注意到線程是併發、交替執行的,下面是可能的執行順序和相應結果:
這些都是意料以內、情理之中的。可是在x86體系結構下,極可能獲得r1=0 r2=0這樣的結果。
若是沒有Memory Consistency,程序員寫的程序代碼的輸出結果是不肯定的。
所以,Memory Consistency就是程序員(編程語言)、編譯器、CPU間的一種協議。這個協議保證了程序訪問內存時會獲得什麼值。
簡單點說,內存一致性,就是保證併發場景下的程序運行結果和程序員預期是同樣的(固然,要經過加鎖等方式),包括的就是併發編程中的原子性、有序性和可見性。而緩存一致性說的就是併發編程中的可見性。
在不少內存模型的實現中,關於緩存一致性的保證都是經過硬件層面緩存一致性協議來保證的。須要注意的是,這裏提到的內存模型,是計算機內存模型,而非Java內存模型。
緩存一致性問題。硬件層面的問題,指的是因爲多核計算機中有多套緩存,各個緩存之間的數據不一致性問題。
PS:這裏還須要再重複一遍,Java多線程中,每一個線程都有本身的工做內存,須要和主存進行交互。這裏的工做內存和計算機硬件的緩存並非一回事兒,只是能夠相互類比。因此,併發編程的可見性問題,是由於各個線程之間的本地內存數據不一致致使的,和計算機緩存並沒有關係。
緩存一致性協議。用來解決緩存一致性問題的,經常使用的是MESI協議。
內存一致性模型。屏蔽計算機硬件問題,主要來解決併發編程中的原子性、有序性和一致性問題。
實現內存一致性模型的時候可能會用到緩存一致性模型。
最後,再給你們留一道思考題:
既然在硬件層面,已經有了緩存一致性協議,能夠保證緩存的一致性即併發編程中的可見性,那麼爲何在寫多線程的代碼的時候,程序員要本身使用volatile、synchronized等關鍵字來保證可見性?
歡迎工做一到五年的Java工程師朋友們加入Java架構開發:744677563
本羣提供免費的學習指導 架構資料 以及免費的解答
不懂得問題均可以在本羣提出來 以後還會有職業生涯規劃以及面試指導