左圖爲高速緩存 右圖爲多級緩存
數據的讀取和存儲都通過高速緩存,CPU核心與高速緩存有一條特殊的快速通道。主存與高速緩存都是鏈接在系統總線上,固然其餘組件也是在此基礎上進行通訊的。
在高速緩存出現後不久,系統變得越發複雜,高速緩存與主存之間的速度差別被拉大,直到加入了另外一級緩存,新加入的這級緩存比第一緩存更大,更慢。若是隻是單純的增大以及還從的容量的話,經濟與技術上是行不通的,因此這纔有了二級緩存,甚至如今有些系統擁有三級緩存。java
由於這方面我不是很懂,因此我把查的一些資料也貼出來,像我同樣的小夥伴也就不用特地去查了: CPU高速緩存(英語:CPU Cache,在本文中簡稱緩存)是用於減小處理器訪問內存所需平均時間的部件。在金字塔式存儲體系中它位於自頂向下的第二層, 僅次於CPU寄存器。其容量遠小於內存,但速度卻能夠接近處理器的頻率。
CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘週期內,CPU經常須要等待主存,浪費資源, 因此cache的出現,是爲了緩解CPU和內存之間速度的不匹配問題(結構:cpu->cache->memort)
· CPU cache有什麼意義?緩存
緩存的容量遠遠小於主存,所以出現緩存不命中的狀況是常有發生,那麼它存在的意義究竟是啥?多線程
1.時間局部性:若是某個數據被訪問,那麼在不久的未來他極可能被再次訪問 2.空間局部性:若是某個數據被訪問,那麼與他相鄰的數據很快也可能被訪問
處理器上有一套完整的協議,來保證Cache一致性。比較經典的Cache一致性協議當屬MESI協議,奔騰處理器有使用它,不少其餘的處理器都是使用它的變種。架構
單核Cache中每一個Cache line有2個標誌:dirty和valid標誌,它們很好的描述了Cache和Memory(內存)之間的數據關係(數據是否有效,數據是否被修改),而在多核處理器中,多個核會共享一些數據,MESI協議就包含了描述共享的狀態。併發
M: Modified 修改,指的是該緩存行只被緩存在該CPU的緩存中,而且是被修改過的,所以他與主存中的數據是不一致的,該緩存行中的數據須要在將來的某個時間點(容許其餘CPU讀取主存相應中的內容以前)寫回主存,而後狀態變成E(獨享) E:Exclusive 獨享 緩存行只被緩存在該CPU的緩存中,是未被修改過的,與主存的數據是一致的,能夠在任什麼時候刻當有其餘CPU讀取該內存時,變成S(共享)狀態,當CPU修改該緩存行的內容時,變成M(被修改)的狀態 S:Share 共享,意味着該緩存行可能會被多個CPU進行緩存,而且該緩存中的數據與主存數據是一致的,當有一個CPU修改該緩存行時,其餘CPU是能夠被做廢的,變成I(無效的) I:Invalid 無效的,表明這個緩存是無效的,多是有其餘CPU修改了該緩存行
M(Modified)和E(Exclusive)狀態的Cache line,數據是獨有的,不一樣點在於M狀態的數據是dirty的(和內存的不一致),E狀態的數據是clean的(和內存的一致)。優化
S(Shared)狀態的Cache line,數據和其餘Core的Cache共享。只有clean的數據才能被多個Cache共享。 I(Invalid)表示這個Cache line無效。
1.用於保證多個CPU cache之間緩存共享數據的一致ui
local read:讀本地緩存的數據
local write:將數據寫到本地緩存裏面
remote read:將內(主)存中的數據讀取到緩存中來
remote write:將緩存中的數據寫會到主存裏面spa
對於上圖我敢確定有好多人應該是不理解的。爲何這麼說呢,由於我就是費了很大的勁才懂得。維基百科原話:
for any given pair of caches the permitted states of
a given cache line are as follows操作系統
(我的理解:當緩存A要對緩存B作操做的時候 緩存A須要將自身變成什麼樣子的狀態)線程
MESI的理解其實就是理解這16種狀態
2.CPU多級緩存的亂序執行優化
處理器爲提升運算速度而作出違背代碼原有順序的優化。固然了在正常狀況下是不對結果形成影響的。在單核時代處理器對結果的優化保證不會遠離預期目標,可是在多核環境下卻並不是如此。爲何這麼說呢?首先,在多核條件下會有多個核執行指令,所以每一個核的指令都有可能會亂序。另外處理器還引入了L一、L2緩存機制,這就致使了邏輯上後寫入的數據不必定最後寫入。
這就致使的一個問題,若是咱們不作任何處理,實際結果可能和邏輯運行結果大不相同。在一個核上記錄一個標誌表示數據已經準備完畢,在另外一個核上來判斷這個數據是否已經就緒,這時候就會存在風險。標記位先被寫入,可是實際的操做缺並未完成,這個未完成既有多是沒有計算完成,也有多是緩存沒有被及時刷新到主存之中,使得其餘核讀到了錯誤的數據。
3.Java內存模型(Java Memory Model,JMM)
爲了屏蔽掉各類系統硬件和操做系統的內存訪問差別,以實現java程序在各大平臺都能達到一致的併發效果,java虛擬機所以定義了java內存模型,它規範了java虛擬機與計算機是如何協同工做的。
它規定了一個線程如何看到或者共享其餘線程一個共享變量的值,以及必須時,如何同步的訪問共享變量。
(這幾段各位懂得小夥伴自動略過哈,我寫是由於我不懂 略顯尷尬)
· JAVA內存模型規範:
1.規定了一個線程如何和什麼時候能夠看到其餘線程修改事後的共享變量的值
2.如何以及什麼時候同步的訪問共享變量
· JAVA內存模型:
Heap(堆):java裏的堆是一個運行時的數據區,堆是由垃圾回收來負責的, 堆的優點是能夠動態的分配內存大小,生存期也沒必要事先告訴編譯器, 由於他是在運行時動態分配內存的,java的垃圾回收器會定時收走不用的數據, 缺點是因爲要在運行時動態分配,全部存取速度可能會慢一些 Stack(棧):棧的優點是存取速度比堆要快,僅次於計算機裏的寄存器,棧的數據是能夠共享的, 缺點是存在棧中的數據的大小與生存期必須是肯定的,缺少一些靈活性 棧中主要存放一些基本類型的變量,好比int,short,long,byte,double,float,boolean,char,對象句柄, java內存模型要求調用棧和本地內存變量存放在線程棧(Thread Stack)上,對象存放在堆上。 一個本地變量可能存放一個對象的引用,這時引用變量存放在本地棧上,可是對象自己存放在堆上 成員變量跟隨着對象存放在堆上,而無論是原始類型仍是引用類型,靜態成員變量跟隨着類的定義一塊兒存在在堆上 存在堆上的對象,能夠被持有這個對象的引用的線程訪問 若是兩個線程同時訪問同一個對象的私有變量,這時他們得到的是這個對象的私有拷貝
CPU:一個計算機通常有多個CPU,一個CPU還會有多核。所以意味着每一個cpu可能都會運行一個線程,因此計算機出現多線程是頗有可能的。 CPU Registers(寄存器):每一個CPU都包含一系列的寄存器,他們是CPU內存的基礎,CPU在寄存器上執行的速度遠大於在主存上執行的速度。 CPU Cache(高速緩存):因爲計算機的存儲設備與處理器的處理設備有着幾個數量級的差距, 因此現代計算機都會加入一層讀寫速度與處理器處理速度接近想通的高級緩存來做爲內存與處理器之間的緩衝, 將運算使用到的數據複製到緩存中,讓運算可以快速的執行,當運算結束後,再從緩存同步到內存之中,這樣,CPU就不須要等待緩慢的內存讀寫了 主(內)存:一個計算機包含一個主存,全部的CPU均可以訪問主存,主存比緩存容量大的多 運做原理:一般狀況下,當一個CPU要讀取主存的時候,他會將主存中的數據讀取到CPU緩存中,甚至將緩存中的內容讀到內部寄存器裏面,而後再寄存器執行操做,當運行結束後,會將寄存器中的值刷新回緩存中,並在某個時間點刷新回主存
內存模型與硬件架構之間的關聯
全部線程棧和堆會被保存在緩存裏面,部分可能會出如今CPU緩存中和CPU內部的寄存器裏面
每一個線程之間共享變量都存放在主內存裏面,每一個線程都有一個私有的本地內存 本地內存是java內存模型中抽象的概念,並非真實存在的(他涵蓋了緩存寫緩衝區。寄存器,以及其餘硬件的優化) 本地內存中存儲了以讀或者寫共享變量的拷貝的一個副本 從一個更低的層次來講,線程本地內存,他是cpu緩存,寄存器的一個抽象描述,而JVM的靜態內存存儲模型, 他只是一種對內存模型的物理劃分而已,只侷限在內存,並且只侷限在JVM的內存 若是線程A和線程B要通訊,必須經歷兩個過程: 一、A將本地內存變量刷新到主內存 二、B從主內存中讀取變量
下面對上面的描述舉個例子。假設主內存的變量爲1,線程A、B同時讀取,線程A從主內存獲得變量值爲1,而後存儲到本身的本地內存,以後進行加一的操做,最後寫回主內存變爲2。其實B的操做也是同樣的。線程B並非等線程A寫回主內存以後再開始操做的,它們之間不可見的,所以計數就出現了錯誤,這就引發了併發的手段。
2.不容許read和load、store和write操做之一單獨出現
3.不容許一個線程丟棄他的最近assign的操做,即變量在工做內存中改變了以後必須同步到主內存中
4.不容許一個線程無緣由地(也就是說必須有assgin操做)把數據從工做內存同步到主內存中
5.一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操做以前,必須先執行過了load和assign操做
6.一個變量在同一時刻只容許一條線程對其進行lock操做,但lock操做能夠同時被一條線程重複執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會解鎖,lock和unlock必須成對出現
7.若是一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎中使用這個變量前須要從新執行load或assign操做初始化變量的值
8.若是一個變量事先沒有被lock操做鎖定,則不容許他執行unlock操做,也不容許去unlock一個被其餘線程鎖定的變量
9.對一個變量執行unlock操做以前,必須先把此變量同步到主內存中(執行store和write操做)
4.併發的優點和風險
做者: 連接:https://www.imooc.com/article/26072?block_id=tuijian_wz 來源:慕課網