java内存分配模型优点_高并发实战(二)-并发基础 缓存 MESI 内存模型
左圖為高速緩存 右圖為多級緩存
數據的讀取和存儲都經過高速緩存,CPU核心與高速緩存有一條特殊的快速通道。主存與高速緩存都是連接在系統總線上,當然其他組件也是在此基礎上進行通信的。
在高速緩存出現后不久,系統變得愈加復雜,高速緩存與主存之間的速度差異被拉大,直到加入了另一級緩存,新加入的這級緩存比第一緩存更大,更慢。如果只是單純的增大以及還從的容量的話,經濟與技術上是行不通的,所以這才有了二級緩存,甚至現在有些系統擁有三級緩存。
因為這方面我不是很懂,所以我把查的一些資料也貼出來,像我一樣的小伙伴也就不用特意去查了:
CPU高速緩存(英語:CPU Cache,在本文中簡稱緩存)是用于減少處理器訪問內存所需平均時間的部件。在金字塔式存儲體系中它位于自頂向下的第二層,
僅次于CPU寄存器。其容量遠小于內存,但速度卻可以接近處理器的頻率。
為什么需要CPU cache?
CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘周期內,CPU常常需要等待主存,浪費資源,
所以cache的出現,是為了緩解CPU和內存之間速度的不匹配問題(結構:cpu->cache->memort)
· CPU cache有什么意義?
緩存的容量遠遠小于主存,因此出現緩存不命中的情況是常有發生,那么它存在的意義到底是啥?
1.時間局部性:如果某個數據被訪問,那么在不久的將來他很可能被再次訪問
2.空間局部性:如果某個數據被訪問,那么與他相鄰的數據很快也可能被訪問
緩存一致性(MESI)?
處理器上有一套完整的協議,來保證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之間緩存共享數據的一致
local read:讀本地緩存的數據
local write:將數據寫到本地緩存里面
remote read:將內(主)存中的數據讀取到緩存中來
remote write:將緩存中的數據寫會到主存里面
對于上圖我敢肯定有好多人應該是不理解的。為什么這么說呢,因為我就是費了很大的勁才懂得。維基百科原話:
for any given pair of caches the permitted states of
a given cache line are as follows
(個人理解:當緩存A要對緩存B做操作的時候 緩存A需要將自身變成什么樣子的狀態)
MESI的理解其實就是理解這16種狀態
2.CPU多級緩存的亂序執行優化
處理器為提高運算速度而做出違背代碼原有順序的優化。當然了在正常情況下是不對結果造成影響的。在單核時代處理器對結果的優化保證不會遠離預期目標,但是在多核環境下卻并非如此。為什么這么說呢?首先,在多核條件下會有多個核執行指令,因此每個核的指令都有可能會亂序。另外處理器還引入了L1、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要通信,必須經歷兩個過程:
1、A將本地內存變量刷新到主內存
2、B從主內存中讀取變量
下面對上面的描述舉個例子。假設主內存的變量為1,線程A、B同時讀取,線程A從主內存得到變量值為1,然后存儲到自己的本地內存,之后進行加一的操作,最后寫回主內存變為2。其實B的操作也是一樣的。線程B并不是等線程A寫回主內存之后再開始操作的,它們之間不可見的,因此計數就出現了錯誤,這就引起了并發的手段。
八種同步規則
1.lock(鎖定):作用于主內存的變量,把一個變量標識變為一條線程獨占狀態
2.unlock(解鎖):作用于主內存的變量,把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定
3.read(讀取):作用于主內存的變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用
4.load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中
5.use(使用):作用于工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎
6.assign(賦值):作用于工作內存的變量,它把一個從執行引擎接受到的值賦值給工作內存的變量
7.store(存儲):作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作
8.write(寫入):作用于主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中
同步規則
1.如果要把一個變量從主內存中賦值到工作內存,就需要按順序得執行read和load操作,如果把變量從工作內存中同步回主內存中,就要按順序得執行store和write操作,但java內存模型只要求上述操作必須按順序執行,沒有保證必須是連續執行
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.并發的優勢和風險
總結
以上是生活随笔為你收集整理的java内存分配模型优点_高并发实战(二)-并发基础 缓存 MESI 内存模型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python内存池机制_python的内
- 下一篇: 迁徙图_虾米音乐上的原住民会迁徙去哪呢?