1.CPU多級緩存
左圖為最簡單的高速緩存的配置,數(shù)據(jù)的讀取和存儲都經(jīng)過高速緩存,CPU核心與高速緩存有一條特殊的快速通道;主存與高速緩存都連在系統(tǒng)總線上(BUS)這條總線還用于其他組件的通信
在高速緩存出現(xiàn)后不久,系統(tǒng)變得越來越復(fù)雜,高速緩存與主存之間的速度差異被拉大,直到加入了另一級緩存,新加入的這級緩存比第一緩存更大,并且更慢,而且經(jīng)濟上不合適,所以有了二級緩存,甚至是三級緩存

CPU多級緩存
為什么需要CPU cache?:
CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘周期內(nèi),CPU常常需要等待主存,浪費資源,
所以cache的出現(xiàn),是為了緩解CPU和內(nèi)存之間速度的不匹配問題(結(jié)構(gòu):cpu->cache->memort)
CPU cache有什么意義:
1.時間局部性:如果某個數(shù)據(jù)被訪問,那么在不久的將來他很可能被再次訪問
2.空間局部性:如果某個數(shù)據(jù)被訪問,那么與他相鄰的數(shù)據(jù)很快也可能被訪問
緩存一致性(MESI)
? M: Modified 修改,指的是該緩存行只被緩存在該CPU的緩存中,并且是被修改過的,因此他與主存中的數(shù)據(jù)是不一致的,
? ? ? 該緩存行中的數(shù)據(jù)需要在未來的某個時間點(允許其他CPU讀取主存相應(yīng)中的內(nèi)容之前)寫回主存,然后狀態(tài)變成E(獨享)
? E:Exclusive 獨享 緩存行只被緩存在該CPU的緩存中,是未被修改過的,與主存的數(shù)據(jù)是一致的,可以在任何時刻當有其他CPU讀取該內(nèi)存時,變成S(共享)狀態(tài),當CPU修改該緩存行的內(nèi)容時,變成M(被修改)的狀態(tài)
? S:Share 共享,意味著該緩存行可能會被多個CPU進行緩存,并且該緩存中的數(shù)據(jù)與主存數(shù)據(jù)是一致的,當有一個CPU修改該緩存行時,其他CPU是可以被作廢的,變成I(無效的)
? I:Invalid 無效的,代表這個緩存是無效的,可能是有其他CPU修改了該緩存行
1.用于保證多個CPU cache之間緩存共享數(shù)據(jù)的一致

MESI
local read:讀本地緩存的數(shù)據(jù)
local write:將數(shù)據(jù)寫到本地緩存里面
remote read:將內(nèi)(主)存中的數(shù)據(jù)讀取到緩存中來
remote write:將緩存中的數(shù)據(jù)寫會到主存里面
在一個典型的多核系統(tǒng)中,每一個核都會有自己的緩存來共享主存總線,每一個CPU會發(fā)出讀寫(I/O)請求,而緩存的目的是為了減少CPU讀寫共享主存的次數(shù);
一個緩存除了在Invaild狀態(tài),都可以滿足CPU 的讀請求
一個寫請求只有在M狀態(tài),或者E狀態(tài)的時候才能給被執(zhí)行,如果是處在S狀態(tài)的時候,他必須先將該緩存行變成I狀態(tài),
這個操作通常作用于廣播的方式來完成,這個時候他既不允許不同的CPU同時修改同一個緩存行,即使是修改同一個緩存行中不同端的數(shù)據(jù)也是不可以的,這里主要解決的是緩存一致性的問題,
一個M狀態(tài)的緩存行必須時刻監(jiān)聽所有試圖讀該緩存行相對主存的操作,這種操作必須在緩存該緩存行被寫會到主存,并將狀態(tài)變成S狀態(tài)之前,被延遲執(zhí)行
一個處于S狀態(tài)的緩存行,也必須監(jiān)聽其他緩存使該緩存行無效,或者獨享該緩存行的請求,并將緩存行變成無效
一個處于E狀態(tài)的緩存行,他要監(jiān)聽其他緩存讀緩存行的操作,一旦有,那么他講變成S狀態(tài)
因此對于M和E狀態(tài),他們的數(shù)據(jù)總是一致的與緩存行的真正狀態(tài)總是保持一致的,
但是S狀態(tài)可能是非一致的,如果一個緩存將處于S狀態(tài)的 緩存行作廢了,另一個緩存可能已經(jīng)獨享了該緩存行,
但是該緩存缺不會講該緩存行升遷為E狀態(tài),這是因為其他緩存不會廣播他們已經(jīng)作廢掉該緩存行的通知,
同樣由于緩存并沒有保存該緩存行被COPY的數(shù)量,因此沒有辦法確定是否獨享了改緩存行,
這是一種投機性的優(yōu)化,因為如果一個CPU想修改一個處于S狀態(tài)的緩存行,總線需要將所有使用該緩存行的COPY的值變成Invaild狀態(tài)才可以,而修改E狀態(tài)的緩存缺不需要使用總顯示無
2.CPU多級緩存的亂序執(zhí)行優(yōu)化
處理器為提高運算速度而做出違背代碼原有順序的優(yōu)化

CPU亂序
導(dǎo)致的一個問題,如果我們不做任何處理,在多核的情況下,的實際結(jié)果可能和邏輯運行結(jié)果大不相同,如果在一個核上執(zhí)行數(shù)據(jù)寫入操作,并在最后執(zhí)行一個操作來標記數(shù)據(jù)已經(jīng)寫入好了,而在另外一個核上通過該標記位判定數(shù)據(jù)是否已經(jīng)寫入,這時候就可能出現(xiàn)不一致,標記位先被寫入,但是實際的操作缺并未完成,這個未完成既有可能是沒有計算完成,也有可能是緩存沒有被及時刷新到主存之中,使得其他核讀到了錯誤的數(shù)據(jù)
3.Java內(nèi)存模型(Java Memory Model,JMM)
JAVA內(nèi)存模型規(guī)范:
1.規(guī)定了一個線程如何和何時可以看到其他線程修改過后的共享變量的值
2.如何以及何時同步的訪問共享變量
JAVA內(nèi)存模型:

java內(nèi)存分配
Heap(堆):java里的堆是一個運行時的數(shù)據(jù)區(qū),堆是由垃圾回收來負責(zé)的,? ? ? ? 堆的優(yōu)勢是可以動態(tài)的分配內(nèi)存大小,生存期也不必事先告訴編譯器,? ? ? ? 因為他是在運行時動態(tài)分配內(nèi)存的,java的垃圾回收器會定時收走不用的數(shù)據(jù),? ? ? ? 缺點是由于要在運行時動態(tài)分配,所有存取速度可能會慢一些Stack(棧):棧的優(yōu)勢是存取速度比堆要快,僅次于計算機里的寄存器,棧的數(shù)據(jù)是可以共享的,? ? ? ? ? 缺點是存在棧中的數(shù)據(jù)的大小與生存期必須是確定的,缺乏一些靈活性? ? ? ? ? 棧中主要存放一些基本類型的變量,比如int,short,long,byte,double,float,boolean,char,對象句柄,java內(nèi)存模型要求調(diào)用棧和本地內(nèi)存變量存放在線程棧(Thread Stack)上,對象存放在堆上。一個本地變量可能存放一個對象的引用,這時引用變量存放在本地棧上,但是對象本身存放在堆上成員變量跟隨著對象存放在堆上,而不管是原始類型還是引用類型,靜態(tài)成員變量跟隨著類的定義一起存在在堆上存在堆上的對象,可以被持有這個對象的引用的線程訪問如果兩個線程同時訪問同一個對象的私有變量,這時他們獲得的是這個對象的私有拷貝
計算機硬件架構(gòu):

計算機架構(gòu)圖示
CPU:一個計算機一般有多個CPU,一個CPU還會有多核
CPU Registers(寄存器):每個CPU都包含一系列的寄存器,他們是CPU內(nèi)存的基礎(chǔ),CPU在寄存器上執(zhí)行的速度遠大于在主存上執(zhí)行的速度。
CPU Cache(高速緩存):由于計算機的存儲設(shè)備與處理器的處理設(shè)備有著幾個數(shù)量級的差距,
? ? ? ? ? ? ? ? ? ? 所以現(xiàn)代計算機都會加入一層讀寫速度與處理器處理速度接近想通的高級緩存來作為內(nèi)存與處理器之間的緩沖,
? ? ? ? ? ? ? ? ? ? 將運算使用到的數(shù)據(jù)復(fù)制到緩存中,讓運算能夠快速的執(zhí)行,當運算結(jié)束后,再從緩存同步到內(nèi)存之中,這樣,CPU就不需要等待緩慢的內(nèi)存讀寫了
主(內(nèi))存:一個計算機包含一個主存,所有的CPU都可以訪問主存,主存比緩存容量大的多
運作原理:通常情況下,當一個CPU要讀取主存的時候,他會將主存中的數(shù)據(jù)讀取到CPU緩存中,甚至將緩存中的內(nèi)容讀到內(nèi)部寄存器里面,然后再寄存器執(zhí)行操作,
當運行結(jié)束后,會將寄存器中的值刷新回緩存中,并在某個時間點刷新回主存
內(nèi)存模型與硬件架構(gòu)之間的關(guān)聯(lián):

內(nèi)存模型與硬件架構(gòu)之間的關(guān)聯(lián)
所有線程棧和堆會被保存在緩存里面,部分可能會出現(xiàn)在CPU緩存中和CPU內(nèi)部的寄存器里面
線程和主內(nèi)存的抽象關(guān)系

線程和主內(nèi)存的抽象關(guān)系
每個線程之間共享變量都存放在主內(nèi)存里面,每個線程都有一個私有的本地內(nèi)存
本地內(nèi)存是java內(nèi)存模型中抽象的概念,并不是真實存在的(他涵蓋了緩存寫緩沖區(qū)。寄存器,以及其他硬件的優(yōu)化)
本地內(nèi)存中存儲了以讀或者寫共享變量的拷貝的一個副本
從一個更低的層次來說,線程本地內(nèi)存,他是cpu緩存,寄存器的一個抽象描述,而JVM的靜態(tài)內(nèi)存存儲模型,
他只是一種對內(nèi)存模型的物理劃分而已,只局限在內(nèi)存,而且只局限在JVM的內(nèi)存
如果線程A和線程B要通信,必須經(jīng)歷兩個過程:
1、A將本地內(nèi)存變量刷新到主內(nèi)存
2、B從主內(nèi)存中讀取變量
八種同步操作
1.lock(鎖定):作用于主內(nèi)存的變量,把一個變量標識變?yōu)橐粭l線程獨占狀態(tài)
2.unlock(解鎖):作用于主內(nèi)存的變量,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定
3.read(讀取):作用于主內(nèi)存的變量,把一個變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中,以便隨后的load動作使用
4.load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
5.use(使用):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎
6.assign(賦值):作用于工作內(nèi)存的變量,它把一個從執(zhí)行引擎接受到的值賦值給工作內(nèi)存的變量
7.store(存儲):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中,以便隨后的write的操作
8.write(寫入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中一個變量的值傳送到主內(nèi)存的變量中
同步規(guī)則
1.如果要把一個變量從主內(nèi)存中賦值到工作內(nèi)存,就需要按順序得執(zhí)行read和load操作,如果把變量從工作內(nèi)存中同步回主內(nèi)存中,就要按順序得執(zhí)行store和write操作,但java內(nèi)存模型只要求上述操作必須按順序執(zhí)行,沒有保證必須是連續(xù)執(zhí)行
2.不允許read和load、store和write操作之一單獨出現(xiàn)
3.不允許一個線程丟棄他的最近assign的操作,即變量在工作內(nèi)存中改變了之后必須同步到主內(nèi)存中
4.不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步到主內(nèi)存中
5.一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執(zhí)行過了load和assign操作
6.一個變量在同一時刻只允許一條線程對其進行l(wèi)ock操作,但lock操作可以同時被一條線程重復(fù)執(zhí)行多次,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作,變量才會解鎖,lock和unlock必須成對出現(xiàn)
7.如果一個變量執(zhí)行l(wèi)ock操作,將會清空工作內(nèi)存中此變量的值,在執(zhí)行引擎中使用這個變量前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值
8.如果一個變量事先沒有被lock操作鎖定,則不允許他執(zhí)行unlock操作,也不允許去unlock一個被其他線程鎖定的變量
9.對一個變量執(zhí)行unlock操作之前,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)