深入底層瞭解Java併發機制系列之CPU緩存模型

Javaer都知道,我們在編譯器上面編寫的Java代碼經過編譯後會形成字節碼,然後由類加載器加載到JVM中,JVM在執行字節碼時,將它們轉換成一條條的彙編指令,最終由CPU的寄存器來運行,在CPU執行這些彙編的過程中需要讀取數據或者寫入數據,但CPU能讀取的數據只能來自計算機中的內存,隨著科技的發展,像Intel的部分CPU頻率特別是睿頻後已經到達了4.3GHZ了,但內存發展就比較緩慢,比如頂級的內存就3600MHZ左右,因此就造成了CPU的處理速度已經遠遠超過了內存的訪問速度,正常情況都是千倍的速度差距。

CPU緩存模型

因為速度差距過大的原因,如果還是採用CPU直接讀取內存上面的數據,就會導致CPU資源嚴重的浪費!於是那些生產CPU的科技公司就設計出了,在CPU和內存之間增加一層緩存的方案,剛才刻意到京東查了一下,現在的CPU基本都是三級緩存了,L1 ,L2 ,L3 緩存,我從百度圖片找來了兩張圖(如有侵權,請聯繫我,我馬上刪除)

深入底層瞭解Java併發機制系列之CPU緩存模型

CPU緩存模型

深入底層瞭解Java併發機制系列之CPU緩存模型

CPU緩存和內存訪問速度對比圖

通過這兩張圖,我們就可以更加直觀地感受到CPU緩存和內存在訪問上面的速度的差距了,至於CPU核心的計算速度,和他們相比又是另一個級別的差距了。

那麼在有了CPU緩存之後,我們就可以在程序運行的過程中,先從內存拷貝一份數據到CPU緩存中,然後CPU計算都操作緩存裡的數據,等執行完成的時候,再把緩存中的數據更新到內存裡,從而增加CPU的使用效率。

深入底層瞭解Java併發機制系列之CPU緩存模型

CPU藉助緩存和內存進行數據交互

在引入CPU緩存之後,主了提高CPU的使用效率之外,還帶來了一個數據不一致的問題。比如i++這一個操作,在引入了CPU緩存之後,他具體的情況是這樣的:

1:將內存中的i複製一份到CPU緩存當中
2:CPU核心讀取CPU緩存中的i
3:對i執行+1操作
4:將更新後的i寫回CPU緩存
5:將CPU緩存中的i更新到內存中

對於單線程來說,這完成不會有什麼問題,但是對於多線程來說,就會出現錯誤了,因為每個線程都有自己的工作空間。比如,現在有線程A和線程B同時對i執行i++操作,我們假設i一開始為0,我們期望最後的結果是2,但是最後的結果可能1:比如:

1:線程A將內存中的i複製一份到CPU緩存當中,此時 i = 0;
2:線程B將內存中的i複製一份到CPU緩存當中,此時 i = 0;
3:線程A對應的CPU核心1讀取CPU緩存中的i,並執行+1操作,然後把更新後的i寫回CPU緩存(i=1)

4:線程B對應的CPU核心2讀取CPU緩存中的i,並執行+1操作,然後把更新後的i寫回CPU緩存(i=1)
5:線程A將CPU緩存中的i更新到內存(i=1)
6:線程B將CPU緩存中的i更新到內存(i=1)

出現這種情況的原因也是很簡單的,比如多個CPU核心都從內存拷貝了一份數據到各自的緩存當中,然後直接拿緩存中的數據來執行+1操作,最後再把數據刷新內存,於是就造成了這個問題。由於Demo過於簡單,我就不給出來了。下面我們回顧一下歷史,看看這個問題是怎麼被解決的,其實解決這個問題的方案有兩種:

第一種是早期的方案,因為CPU和計算機的其他組件通信是通過總線來進行的,
比如數據通信就是通過數據總線來進行,如果一個CPU核心要操作某個數據了,
就通過向總線發送一個LOCK#的信號來獲取總線鎖,那麼其他CPU核心就被阻塞了,
從而只有一個CPU核心能對內存進行訪問。

但是這種方案明顯效率是比較低的,於是就提出了第二方案:

通過緩存一致性協議來解決數據不一致的問題,即CPU在操作CPU緩存中的數據時, 

如果發現它是一個共享變量(其他CPU也緩存了一個副本),那麼他會進行以下的兩種操作:
(1) 讀操作,只會將數據單純讀到寄存器,不做額外處理
(2) 寫操作,發出一個信號告訴其他CPU核心,你緩存的數據已經無效啦,讓其他CPU在讀取共享變量時,不得不重新去內存中重新拿過數據。

至此CPU緩存模型我們已經介紹的差不多了,下一篇我們去了解Java內存模型,有了CPU緩存模型和Java內存模型的知識,我們重新認識Java高併發又是另一種理解境界,下期見。


分享到:


相關文章: