并发编程-02并发基础CPU多级缓存和Java内存模型JMM
文章目錄
- CPU多級緩存
- CPU多級緩存概述
- CPU 多級緩存-緩存一致性協(xié)議MESI
- CPU 多級緩存-亂序執(zhí)行優(yōu)化-重排序
- JAVA內存模型 (JMM)
- 計算機硬件架構簡易圖示
- JAVA內存模型與硬件架構之間的關系
- Java內存模型的抽象結構
- Java內存模型的同步八種操作
- Java內存模型 - 同步規(guī)則
- 并發(fā)編程優(yōu)缺點
- 代碼
CPU多級緩存
CPU多級緩存概述
為什么CPU緩存會分為一級緩存L1、L2、L3?有什么意義?
CPU的頻率非常快,主存Main Memory跟不上。CPU緩存是CPU與內存之間的臨時數(shù)據(jù)交換器,為了解決CPU運行處理速度與內存讀寫速度不匹配的矛盾——緩存的速度比內存的速度快多了。
上圖左側為簡易的高速緩存結構,數(shù)據(jù)的讀取和存儲都經(jīng)過高速緩存Cache,CPU核心與高速緩存有一條特殊的快速通道;主存Main Memory與高速緩存都連在系統(tǒng)總線上(BUS)這條總線還用于其它組件的通信。
在高速緩存出現(xiàn)后不久,系統(tǒng)變得愈加復雜,高速緩存與主存之間的速度差異被拉大,直到加入了L2 Cache ,甚至L3 Cache。它們的作用都是
- 作為CPU與主內存之間的高速數(shù)據(jù)緩沖區(qū),L1最靠近CPU核心;L2其次;L3再次。
- 運行速度方面:L1>L2>L3
- 容量大小方面:L1<L2<L3
CPU會先在最快的L1中尋找需要的數(shù)據(jù),找不到再去找次快的L2,還找不到再去找L3,L3都沒有那就只能去內存找了。如上圖右側。
CPU 多級緩存-緩存一致性協(xié)議MESI
MESI協(xié)議的作用:用于保證多個CPU Cache之間緩存共享數(shù)據(jù)的一致
MESI 是指4中狀態(tài)的首字母。每個Cache line有4個狀態(tài),可用2個bit表示,它們分別是:
注: 緩存行(Cache line):緩存存儲數(shù)據(jù)的單元。
| M 修改 (Modified) | 該Cache line有效,數(shù)據(jù)被修改了,和內存中的數(shù)據(jù)不一致,數(shù)據(jù)只存在于本Cache中 | 緩存行必須時刻監(jiān)聽所有試圖讀該緩存行相對就主存的操作,這種操作必須在緩存將該緩存行寫回主存并將狀態(tài)變成S(共享)狀態(tài)之前被延遲執(zhí)行 |
| E 獨享、互斥 (Exclusive) | 該Cache line有效,數(shù)據(jù)和內存中的數(shù)據(jù)一致,數(shù)據(jù)只存在于本Cache中 | 緩存行也必須監(jiān)聽其它緩存讀主存中該緩存行的操作,一旦有這種操作,該緩存行需要變成S(共享)狀態(tài) |
| S 共享 (Shared) | 該Cache line有效,數(shù)據(jù)和內存中的數(shù)據(jù)一致,數(shù)據(jù)存在于很多Cache中 | 緩存行也必須監(jiān)聽其它緩存使該緩存行無效或者獨享該緩存行的請求,并將該緩存行變成無效(Invalid) |
| I 無效 (Invalid) | 該Cache line無效 | 無 |
觸發(fā)事件:
| 本地讀取(Local read) | 本地cache讀取本地cache數(shù)據(jù) |
| 本地寫入(Local write) | 本地cache寫入本地cache數(shù)據(jù) |
| 遠端讀取(Remote read) | 其它cache讀取本地cache數(shù)據(jù) |
| 遠端寫入(Remote write) | 其它cache寫入本地cache數(shù)據(jù) |
CPU 多級緩存-亂序執(zhí)行優(yōu)化-重排序
處理器為提高運算速度而做出違背代碼原有順序的優(yōu)化, 線程下的情況下可見性就會出現(xiàn)問題。
在正常情況下是不對結果造成影響的。在單核時代處理器對結果的優(yōu)化保證不會遠離預期目標,但是在多核環(huán)境下卻并非如此. 因為在多核條件下會有多個核執(zhí)行指令,因此每個核的指令都有可能會亂序。另外處理器還引入了L1、L2緩存機制,這就導致了邏輯上后寫入的數(shù)據(jù)不一定最后寫入。
在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序。重排序分為下面三種:
編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。
指令級并行的重排序。現(xiàn)代處理器采用了指令集并行技術來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴,處理器可以改變語句對應機器指令的執(zhí)行順序。
內存系統(tǒng)的重排序。由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能可能是在亂序執(zhí)行。
編譯器重排序屬于編譯器重排序,指令級并行重排序和內存系統(tǒng)重排序屬于處理器重排序,這些重排序可能會導致多線程出現(xiàn)內存可見性問題。
JAVA內存模型 (JMM)
上面講的是硬件CPU的多級緩存,為了屏蔽掉各種系統(tǒng)硬件和操作系統(tǒng)的內存訪問差異,以實現(xiàn)Java程序在各大平臺都能達到一致的并發(fā)效果,Java虛擬機因此定義了Java內存模型,它規(guī)范了Java虛擬機與計算機是如何協(xié)同工作的。
接著說上面的重排序,對于JVM來講是怎樣的呢?
那JMM(Java Memory Model)
關于JVM內存區(qū)域中的堆棧請參考 JVM-01Java內存區(qū)域與內存溢出異常(上)【運行時區(qū)域數(shù)據(jù)】
存在堆上的對象,可以被持有這個對象的引用的線程訪問。如果兩個線程同時訪問同一個對象的私有變量,此時這兩個線程所擁有的是"這個對象的私有拷貝"。 比如這里的Object3
計算機硬件架構簡易圖示
CPU:一個計算機一般有多個CPU,一個CPU還會有多核。因此意味著每個CPU可能都會運行一個線程,所以計算機出現(xiàn)多線程是很有可能的。
CPU Registers(寄存器):每個CPU都包含一系列的寄存器,它們是CPU內存的基礎,CPU在寄存器上執(zhí)行的 速度遠大于在主存上執(zhí)行的速度,這是因為計算機訪問寄存器的速度遠大于主存。
CPU Cache(高速緩存):由于計算機的存儲設備與處理器的處理設備有著幾個數(shù)量級的差距,所以現(xiàn)代計 算機都會加入一層讀寫速度與處理器處理速度接近相同的高級緩存來作為內存與處理器之間的緩沖,將運算使用到的數(shù)據(jù)復制到緩存中,讓運算能夠快速的執(zhí)行,當運算結束后,再從緩存同步到內存之中,這 樣,CPU就不需要等待緩慢的內存讀寫了主(內)存 。 一個計算機包含一個主存,所有的CPU都可以訪問主存,主存比緩存容量大的多(CPU訪問緩存層的速度快于訪問主存的速度!但通常比訪問內存寄存器的速度還是要慢點)。
通常情況下,當一個CPU要讀取主存(RAM - Main Mernory)的時候,它首先會將主存中的數(shù)據(jù)讀 取到CPU緩存中,甚至將緩存內容讀到內部寄存器里面,然后再寄存器執(zhí)行操作,當運行結束后,會將寄存器中的值刷新回緩存中,并在某個時間點將值刷新回主存。
JAVA內存模型與硬件架構之間的關系
右側的硬件內存模型是沒有區(qū)分線程 Stack棧 和 Heap堆,對于硬件而言,所有的棧和堆分布在主存里面,部分棧和堆也可能出現(xiàn)在CPU緩存以及CPU內部的寄存器中。
Java內存模型的抽象結構
如果線程A和線程B要通信的話,必須要經(jīng)歷下面兩個步驟
使用如下示意圖更加清晰
假設這3個內存中x均為0,線程A執(zhí)行時將更新后的值假設更新為1臨時存放到自己的本地內存A中。 當線程A和B需要通信時,線程A首先會把自己本地內存中的修改后的值即1刷新到主內存中,此時主內存中x=1. 隨后線程B到主內存中去讀取線程A更新后的值,此時線程B的本地內存的x值也變成了1. 【正常情況 A先B后】
如果線A和線程B同時讀取到主內存中的x值,均為0 ,線程A將x值更新為1,放到線程A本地內存,因為線程A和線程B它們之間的數(shù)據(jù)不可見,線程B并沒有等線程A寫回主內存之后做更新操作 ,此時線程B也做了同樣的更新操作,這個時候線程B的本地內存中x也變成了1 ,因此當線程B操作完成將結果1寫回主內存時計數(shù)就出現(xiàn)了錯誤【因為線程B并沒有等線程A將更新后數(shù)據(jù)寫會主內存】,正確的情況應該是線程B讀取主內存中的1,然后更新為2,再次寫會主內存,主內存最后的x=2. 這就引起了并發(fā)問題。這就解釋了我們案例中的count為啥不總是等于1萬的情況 , 案例-> https://blog.csdn.net/yangshangwei/article/details/87400938#_48 【異常情況 AB同時執(zhí)行】
所以需要使用同步的手段去確保程序處理的準確性。
從整體上看,這兩個步驟實質上是線程A向線程B發(fā)送消息,而且這個通信過程必須要經(jīng)過主內存。 JMM通過控制主內存與每個線程的本地內存之間的交互,來提供內存可見性保證 。
Java內存模型的同步八種操作
Lock(鎖定):作用于主內存的變量,把一個變量標識變?yōu)橐粭l線程獨占狀態(tài)
Unlock(解鎖):作用于主內存的變量,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其它線程鎖定
Read(讀取):作用于主內存的變量,把一個變量值從主內存?zhèn)鬏數(shù)骄€程的工作內存中,以便隨后的load動作使用
Load(載入):作用于工作內存的變量,它把Read操作從主內存中得到的變量值放入工作內存的變量副本中
Use(使用):作用于工作內存的變量,把工作內存中的一個變量值傳遞給執(zhí)行引擎
Assign(賦值):作用于工作內存的變量,它把一個從執(zhí)行引擎接受到的值賦值給工作內存的變量
Store(存儲):作用于工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以便隨后的write的操作
Write(寫入):作用于主內存的變量,它把Store操作從工作內存中一個變量的值傳送到主內存的變量中
Java內存模型 - 同步規(guī)則
并發(fā)編程優(yōu)缺點
代碼
https://github.com/yangshangwei/ConcurrencyMaster
總結
以上是生活随笔為你收集整理的并发编程-02并发基础CPU多级缓存和Java内存模型JMM的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 并发编程-01并发初窥
- 下一篇: java美元兑换,(Java实现) 美元