在多線程併發環境下,常常出現一些意想不到的錯誤,例如數值的累加,錯的緣由可能涉及到計算機原理以及JAVA方面的一些知識。程序員
下面咱們就先從CPU的多級緩存開始提及;緩存
緩存存在的意義:服務器
CPU緩存存在的意義分兩點(局部性原理):多線程
咱們先來認識一下總體結構,看一張圖片,以下:併發
CPU Core : CPU核心ide
Cache : 高速緩存,數據的讀取和存儲都通過此高速緩存學習
Main Memory : 內存/主存優化
緩存的工做原理是當CPU要讀取一個數據時,首先從緩存中查找,若是找到就當即讀取並運送給CPU處理;若是沒有找到,就用相對慢的速度內存中讀取並運送給CPU處理,同時把這個數據所在的數據塊調入緩存中,可使得之後對整塊數據的讀取都從緩存中進行,沒必要再調用內存。操作系統
這裏只涉及到了一個高速緩存Cache,那爲何後來有了緩存的分級呢?有了L1-Cache,L2-Cache,L3-Cache呢?要想弄懂這些首先咱們要明白咱們爲何須要緩存?線程
由於CPU的頻率太快了,快到內存/主存跟不上,這樣在處理器時鐘週期內,CPU經常須要等待主存,浪費資源,這樣會使CPU花費很長時間等待數據到來或把數據寫入內存。因此Cache的出現,是爲了解決CPU運行處理速度與內存讀寫速度不匹配的矛盾(結構:CPU - > CACHE - > Main Memory - > SSD)
咱們先不說緩存爲何須要分級?能夠想一下,若是是你你想要一個什麼樣的緩存器/存儲器,或者說你想設計一個什麼樣子的緩存Cache;速度快、體積小、空間大、能耗低、散熱好、斷電數據不丟失等等都是咱們想要的;但在現實中,咱們每每沒法把全部需求都實現。
你可能還會問,那幹嘛不把內存放到 CPU 裏?
若是你這麼作的話,除了整個電路散熱和體積會出現問題,服務器也沒有辦法作定製內存了。也就是說 CPU 在出廠時就決定了它的內存大小,若是你想換更大的內存,就要換 CPU,而組裝定製化是你很是重要的訴求,這確定是不能接受的。
此外,在相同價格下,速度越快,那麼它的能耗一般越高。能耗越高,發熱量越大。
所以,咱們上面提到的需求是不可能被所有知足的,除非未來哪天存儲技術有顛覆性的突破。
綜上所訴,咱們不能用一塊存儲器來解決全部的需求,那就必須把需求分級。
一種可行的方案,就是根據數據的使用頻率使用不一樣的存儲器:高頻使用的數據,讀寫越快越好,所以用最貴的材料,放到離 CPU 最近的位置;使用頻率越低的數據,咱們放到離 CPU 越遠的位置,用越便宜的材料。
這裏咱們就增長了L1-Cache,L2-Cache,L3-Cache這三級緩存都是集成在CPU內的緩存;它們的做用都是做爲CPU與主內存之間的高速數據緩衝區,L1最靠近CPU核心;L2其次;L3再次。運行速度方面:L1最快、L2次快、L3最慢;容量大小方面:L1最小、L2較大、L3最大。CPU會先在最快的L1中尋找須要的數據,找不到再去找次快的L2,還找不到再去找L3,L3都沒有那就只能去內存找了。L一、L二、L3能夠說是各有特色,下面咱們就分開來說一下。
在講解以前咱們先回顧一下在上一節中對寄存器大體講解了一下,那寄存器的位置在哪裏呢?其實寄存器是距離CPU核心是最近的;
寄存器緊挨着 CPU 的控制單元和邏輯計算單元,它所使用的材料速度也是最快的。就像咱們前面講到的,存儲器的速度越快、能耗越高、產熱越大,並且花費也是最貴的,所以數量不能不少。
寄存機的訪問速度很是快,通常要求在半個 CPU 時鐘週期內完成讀寫。好比一條要在 4 個週期內完成的指令,除了讀寫寄存器,還須要解碼指令、控制指令執行和計算。若是寄存器的速度太慢,那 4 個週期就可能沒法完成這條指令了。
L1-Cache
L1- 緩存在 CPU 中,相比寄存器,雖然它的位置距離 CPU 核心更遠,但造價更低。一般 L1-Cache 大小在幾十 Kb 到幾百 Kb 不等,讀寫速度在 2~4 個 CPU 時鐘週期。
L2-Cache
L2- 緩存也在 CPU 中,位置比 L1- 緩存距離 CPU 核心更遠。它的大小比 L1-Cache 更大,具體大小要看 CPU 型號,有 2M 的,也有更小或者更大的,速度在 10~20 個 CPU 週期。
L3-Cache
L3- 緩存一樣在 CPU 中,位置比 L2- 緩存距離 CPU 核心更遠。大小一般比 L2-Cache 更大,讀寫速度在 20~60 個 CPU 週期。L3 緩存大小也是看型號的,好比 i9 CPU 有 512KB L1 Cache;有 2MB L2 Cache; 有16MB L3 Cache。
內存
內存的主要材料是半導體硅,是插在主板上工做的。由於它的位置距離 CPU 有一段距離,因此須要用總線和 CPU 鏈接。由於內存有了獨立的空間,因此體積更大,造價也比上面提到的存儲器低得多。如今有的我的電腦上的內存是 16G,但有些服務器的內存能夠到幾個 T。內存速度大概在 200~300 個 CPU 週期之間。
SSD 和硬盤
SSD 也叫固態硬盤,結構和內存相似,可是它的優勢在於斷電後數據還在。內存、寄存器、緩存斷電後數據就消失了。內存的讀寫速度比 SSD 大概快 10~1000 倍。之前還有一種物理讀寫的磁盤,咱們也叫做硬盤,它的速度比內存慢 100W 倍左右。由於它的速度太慢,如今已經逐漸被 SSD 替代。
當 CPU 須要內存中某個數據的時候,若是寄存器中有這個數據,咱們能夠直接使用;若是寄存器中沒有這個數據,咱們就要先查詢 L1 緩存;L1 中沒有,再查詢 L2 緩存;L2 中沒有再查詢 L3 緩存;L3 中沒有,再去內存中拿。
接下來咱們討論下指令預讀的問題。
以前咱們學過,CPU 順序執行內存中的指令,CPU 執行指令的速度是很是快的,通常是 26 個 CPU 時鐘週期;這節課,咱們學習了存儲器分級策略,發現內存的讀寫速度實際上是很是慢的,大概有 200300 個時鐘週期。
不知道你發現沒有?這也產生了一個很是麻煩的問題:CPU 實際上是不能從內存中一條條讀取指令再執行的,若是是這樣作,那每執行一條指令就須要 200~300 個時鐘週期了。
那麼,這個問題如何處理呢?
這裏我再多說一句,你在作業務開發 RPC 調用的時候,其實也會常常碰到這種狀況,遠程調用拖慢了總體執行效率,下面咱們一塊兒討論這類問題的解決方案。
一個解決辦法就是 CPU 把內存中的指令預讀幾十條或者上百條到讀寫速度較快的 L1- 緩存中,由於 L1- 緩存的讀寫速度只有 2~4 個時鐘週期,是能夠跟上 CPU 的執行速度的。
這裏又產生了另外一個問題:若是數據和指令都存儲在 L1- 緩存中,若是數據緩存覆蓋了指令緩存,就會產生很是嚴重的後果。所以,L1- 緩存一般會分紅兩個區域,一個是指令區,一個是數據區。
與此同時,又出現了一個問題,L1- 緩存分紅了指令區和數據區,那麼 L2/L3 需不須要這樣分呢?其實,是不須要的。由於 L2 和 L3,不須要協助處理指令預讀的問題。
接下來,還有一個重要的問題須要解決。就是 L1/L2/L3 加起來,緩存的命中率有多少?
所謂命中就是指在緩存中找到須要的數據。和命中相反的是穿透,也叫 miss,就是一次讀取操做沒有從緩存中找到對應的數據。
據統計,L1 緩存的命中率在 80% 左右,L1/L2/L3 加起來的命中率在 95% 左右。所以,CPU 緩存的設計仍是至關合理的。只有 5% 的內存讀取會穿透到內存,95% 都能讀取到緩存。 這也是爲何程序語言逐漸取消了讓程序員操做寄存器的語法,由於緩存保證了很高的命中率,多餘的優化意義不大,並且很容易出錯。