高併發第二彈:併發概念及內存模型(JMM)

高併發第二彈:併發概念及內存模型(JMM)html

感謝 :java

深刻Java內存模型 http://www.importnew.com/10589.html, cpu緩存一致性 https://www.cnblogs.com/yanlong300/p/8986041.html;

1.概念程序員

  • 併發:同時擁有兩個或者多個線程,若是程序在單核處理器上運行,多個線程將交替地還如或者換出內存,這些線程是同時」存在」的,每隔線程都處於執行過程當中的某個狀態,若是運行在多核處理器上,此時,程序中的每一個線程都將分配到一個處理器核上,所以能夠同時運行。緩存

  • 高併發:High Concurrency 是互聯網分佈式系統架構設計中必須考慮的因素之一,它一般是指,經過設計保證系統可以同時並行處理不少請求。安全

2. 多級緩存架構

CPU的頻率太快了,快到主存跟不上,這樣在處理器時鐘週期內,CPU經常須要等待主存,浪費資源,因此cache的出現,是爲了緩解CPU和內存之間速度的不匹配問題。 
CPU多級緩存配置(演變): 併發

局部性原理: 
(1) 時間局部性:若是某個數據被訪問,那麼在不久的未來它極可能被再次訪問。 
(2) 空間局部性:若是某個數據被訪問,那麼與他相鄰的數據很快也可能被訪問。分佈式

請注意,這只是示意圖,真正的數據流並不須要流經上級緩存高併發

3.緩存一致性(MESI Modify|Exclusive|Share|Invalid)性能

 

若是尚未概念,建議看一下 :[每一個程序員都應該瞭解的 CPU 高速緩存]https://www.oschina.net/translate/what-every-programmer-should-know-about-cpu-cache-part2?cmp

  1.1 cpu緩存一致性協議MESI >> 更詳細:cpu緩存一致性

  MESI 是指4中狀態的首字母。每一個Cache line有4個狀態,可用2個bit表示,它們分別是:

    緩存行(Cache line):緩存存儲數據的單元。

狀態 描述 監放任務
M 修改 (Modified) 該Cache line有效,數據被修改了,和內存中的數據不一致,數據只存在於本Cache中。 緩存行必須時刻監聽全部試圖讀該緩存行相對就主存的操做,這種操做必須在緩存將該緩存行寫回主存並將狀態變成S(共享)狀態以前被延遲執行。
E 獨享、互斥 (Exclusive) 該Cache line有效,數據和內存中的數據一致,數據只存在於本Cache中。 緩存行也必須監聽其它緩存讀主存中該緩存行的操做,一旦有這種操做,該緩存行須要變成S(共享)狀態。
S 共享 (Shared) 該Cache line有效,數據和內存中的數據一致,數據存在於不少Cache中。 緩存行也必須監聽其它緩存使該緩存行無效或者獨享該緩存行的請求,並將該緩存行變成無效(Invalid)。
I 無效 (Invalid) 該Cache line無效。

MESI狀態轉換

理解該圖的前置說明:
1.觸發事件

觸發事件 描述
本地讀取(Local read) 本地cache讀取本地cache數據
本地寫入(Local write) 本地cache寫入本地cache數據
遠端讀取(Remote read) 其餘cache讀取本地cache數據
遠端寫入(Remote write) 其餘cache寫入本地cache數據

2.cache分類:
前提:全部的cache共同緩存了主內存中的某一條數據。

本地cache:指當前cpu的cache。
觸發cache:觸發讀寫事件的cache。
其餘cache:指既除了以上兩種以外的cache。
注意:本地的事件觸發 本地cache和觸發cache爲相同。

下圖示意了,當一個cache line的調整的狀態的時候,另一個cache line 須要調整的狀態。

  M E S I
M × × ×
E × × ×
S × ×
I

舉個栗子來講:

假設cache 1 中有一個變量x = 0的cache line 處於S狀態(共享)。
那麼其餘擁有x變量的cache 二、cache 3等x的cache line調整爲S狀態(共享)或者調整爲 I 狀態(無效)。

更詳細的 我建議去我剛剛分享的那個師兄去看,更詳細

    • 四種操做
    • 本地讀取 local read :讀本地緩存
    • 本地寫入 local write : 寫本地緩存
    • 遠端讀取 remote rade : 將Memory中的數據讀取過來
    • 遠端寫入 remote write : 將數據寫回Memory中 
      緩存被修改時的狀況: 
      這裏寫圖片描述 
      某一時刻緩存被CPU A 與CPU B共享,這時CPU A 要修改本地緩存的時候,會將主存的數據與CPU B在共享的數據置爲無效狀態。緩存由S -> I

4.亂序執行優化

void executedOnCpu0() {
     value = 10 ;
     finished = true ;
}
 
void executedOnCpu1() {
     while (!finished);
     assert value == 10 ;
}

試想一下開始執行時,CPU 0保存着finishedExclusive狀態,而value並無保存在它的緩存中。(例如,Invalid)。在這種狀況下,value會比finished更遲地拋棄存儲緩存。徹底有可能CPU 1讀取finished的值爲true,而value的值不等於10

這種在可識別的行爲中發生的變化稱爲重排序(reordings)。注意,這不意味着你的指令的位置被惡意(或者好意)地更改。

它只是意味着其餘的CPU會讀到跟程序中寫入的順序不同的結果。

 

五、JAVA 內存模型(JMM)

 一種規範,規範了java虛擬機與計算機內存如何協同工做的。它規定了**一個線程如何和什麼時候能夠看到其餘線程修改過的共享變量的值,以及在必須時如何同步地訪問共享變量**。

  • 堆Heap:運行時數據區,有垃圾回收,堆的優點能夠動態分配內存大小,生存期也沒必要事先告訴編譯器,由於他是在運行時動態分配內存。缺點是因爲運行時動態分配內存,因此存取速度慢一些。
  • 棧Stack:優點存取速度快,速度僅次於計算機的寄存器。棧的數據是能夠共享的,可是缺點是存在棧中數據的大小與生存期必須是肯定的。主要存放基本類型變量,對象據點。要求調用棧和本地變量存放在線程棧上。
  • 靜態類型變量跟隨類的定義存放在堆上。存放在堆上的對象能夠被所持有對這個對象引用的線程訪問。
  • 若是兩個線程同時調用了同一個對象的同一個方法,他們都會訪問這個對象的成員變量。可是這兩個線程都擁有的是該對象的成員變量(局部變量)的私有拷貝。—[線程封閉中的堆棧封閉]

這裏寫圖片描述

  • CPU Registers(寄存器):是CPU內存的基礎,CPU在寄存器上執行操做的速度遠大於在主存上執行的速度。這是由於CPU訪問寄存器速度遠大於主存。
  • CPU Cache Memory(高速緩存):因爲計算機的存儲設備與處理器的運算速度之間有着幾個數量級的差距,因此現代計算機系統都不得不加入一層讀寫速度儘量接近處理器運算速度的高級緩存,來做爲內存與處理器之間的緩衝。將運算時所使用到的數據複製到緩存中,讓運算能快速的進行。當運算結束後,再從緩存同步回內存之中,這樣處理器就無需等待緩慢的內存讀寫了。
  • RAM-Main Memory(主存/內存):
  • 當一個CPU須要讀取主存的時候,他會將主存中的部分讀取到CPU緩存中,甚至他可能將緩存中的部份內容讀到他的內部寄存器裏面,而後在寄存器中執行操做。當CPU須要將結果回寫到主存的時候,他會將內部寄存器中的值刷新到緩存中,而後在某個時間點從緩存中刷回主存。

這裏寫圖片描述Java內存模型抽象結構:每一個線程都有一個私有的本地內存,本地內存他是java內存模型的一個抽象的概念。它並非真實存在的,它涵蓋了緩存、寫緩衝區、寄存器以及其餘的硬件和編譯器的優化。本地內存中它存儲了該線程以讀或寫共享變量拷貝的一個副本。從更低的層次來講,主內存就是硬件的內存,是爲了獲取更高的運行速度,虛擬機及硬件系統可能會讓工做內存優先存儲於寄存器和高速緩存中,java內存模型中的線程的工做內存是CPU的寄存器和高速緩存的一個抽象的描述。而JVM的靜態內存存儲模型它只是對內存的一種物理劃分而已。它只侷限在內存,並且只侷限在JVM的內存。 Write One,Run Everywhere!

六、Java內存模型-同步八種操做

這裏寫圖片描述

  • lock(鎖定) :做用於主內存變量,把一個變量標識爲一條線程獨佔狀態
  • unlock(解鎖) : 做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定
  • read(讀取) : 做用於主內存的變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用
  • load(載入) :做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中
  • use(使用) :做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎
  • assign(賦值) : 做用於工做內存的變量,它把一個從執行引擎接收到的值賦值給工做內存的變量
  • store(存儲) : 做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,以便隨後的write操做
  • write(寫入) :做用於主內存的變量中,它把store操做從工做內存中一個變量的值傳送到主內存的變量中

 

七、Java內存模型-同步規則

  • 若是要把一個變量從主內存中複製到工做內存,就須要按順序的執行read和load操做,若是把變量從工做內存中同步回主內存中,就要按順序的執行store和write操做。但java內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行
  • 不容許read和load、store和write操做之一單獨出現
  • 不容許一個線程丟棄它的最近assign的操做,即變量在工做內存中改變了以後必須同步到主內存中
  • 不容許一個線程無緣由的(沒有發生過任何assign操做)把數據從工做內存同步回主內存中
  • 一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做。
  • 一個變量早同一時刻只容許一條線程對其進行lock操做,但lock操做能夠被同一條線程重複執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會被解鎖。lock和unlock必須是成對出現。
  • 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前須要從新執行load或assign操做初始化變量的值。
  • 若是一個變量事先沒有被lock鎖定,則不容許對它執行unlock操做,也不容許unlock一個被其餘線程鎖定的變量
  • 對一個變量執行unlock操做以前,必須先把此變量同步到主內存中(執行store和write操做)

八、併發的優點與風險

風險:

  • 安全性:多個線程共享數據時可能會產生於指望不相符的結果
  • 活躍性:某個操做沒法繼續進行下去時,就會發生活躍性問題。好比死鎖、飢餓問題
  • 性能:線程過多時會使得CPU頻繁切換,調度時間增多;同步機制;消耗過多內存。

優點:

  • 速度:同時處理多個請求,響應更快;複雜的操做能夠分紅多個進程同時進行。
  • 設計:程序設計在某些狀況下更簡單,也能夠有更多選擇
  • 資源利用:CPU可以在等待IO的時候作一些其餘的事情
相關文章
相關標籤/搜索