共享內存模型java
在共享內存的併發模型裏面,線程之間共享程序的公共狀態,線程之間經過讀寫內存中公共狀態來進行隱式通訊數組
該內存指的是主內存,其實是物理內存的一小部分緩存
線程安全 : 局部變量、方法定義的參數、異常處理器參數是當前線程的虛擬機棧中的數據,而且不會進行線程共享,因此不會存在內存可見性問題安全
由上圖能看出來線程間的通信都是經過主內存來進行傳遞消息的, 每一個線程在進行共享數據處理的時候都是將共享的數據複製到當前線程本地(每一個線程本身都有一個內存)來進行操做。多線程
整個數據交互的過程是JMM控制的,主要控制主內存與每一個線程的本地內存如何進行交互來提供共享數據的可見性併發
程序在執行的時候爲了提升效率會將程序指令進行從新排序app
編譯器在不改變單線程程序語義的狀況下進行語句執行順序的優化ide
若是不存在數據的依賴性的話,處理器能夠改變語句對應機器指令的執行順序優化
因爲處理器使用緩存和讀/寫緩衝區,這使得加載和存儲操做看上去多是在亂序執行線程
以上三種重排序都會致使咱們在寫併發程序的時候出現內存可見性的問題。
JMM的編譯器重排序規則會禁止特定類型的編譯器重排序;
JMM的處理器重排序規則會要求java編譯器在生成指令序列的時候插入特定的內存屏障指令,經過內存屏障指令來禁止特定類型的處理器進行重排序
因爲爲了不處理器等待向內存中寫入數據的延時,在處理器和內存中間加了一個緩衝區,這樣處理器能夠一直向緩衝區中寫入數據,等到必定時間將緩衝區的數據一次性的刷入到內存中。
優勢 :
缺點 :
例如如下場景 :
在當前場景中就可能出如今處理器 A 和處理器 B 沒有將它們各自的寫緩衝區中的數據刷回內存中, 將內存中讀取的A = 0、B = 0 進行給X和Y賦值,此時將緩衝區的數據刷入內存,致使了最後結果和實際想要的結果不一致。由於只有將緩衝區的數據刷入到了內存中才叫真正的執行
以上主內存與工做內存之間的具體交互協議,即一個變量如何從主內存拷貝到工做內存,如何從工做內存同步到主內存之間的實現細節,JMM定義瞭如下8種操做來完成
操做 | 語義解析 |
---|---|
lock(鎖定) | 做用於主內存的變量,把一個變量標記爲一條線程獨佔狀態 |
unlock(解鎖) | 做用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定 |
read(讀取) | 做用於主內存的變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用 |
load(載入) | 做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中 |
use(使用) | 做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎 |
assign(賦值) | 做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量 |
store(存儲) | 做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,<br>以便隨後的write的操做 |
write(寫入) | 做用於工做內存的變量,它把store操做從工做內存中的一個變量的值傳送<br>到主內存的變量中 |
若是要把一個變量從主內存中複製到工做內存中,就須要按順序地執行read和load操做,若是把變量從工做內存中同步到主內存中,就須要按順序地執行store和write操做。但Java內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行
操做執行流程圖解:
同步規則分析
爲了解決處理器重排序致使的內存錯誤,java編譯器在生成指令序列的適當位置插入內存屏障指令,來禁止特定類型的處理器重排序
內存屏障指令
屏障類型 | 指令示例 | 說明 |
---|---|---|
LoadLoadBarriers | Load1;LoadLoad;Load2 | Load1數據裝載發生在Load2及其全部後續數據裝載以前 |
StoreStoreBarriers | Store1;StoreStore;Store2 | Store1數據刷回主存要發生在Store2及其後續全部數據刷回主存以前 |
LoadStoreBarriers | Load1;LoadStore;Store2 | Load1數據裝載要發生在Store2及其後續全部數據刷回主存以前 |
StoreLoadBarriers | Store1;StoreLoad;Load2 | Store1數據刷回內存要發生在Load2及其後續全部數據裝載以前 |
happens-before 原則來輔助保證程序執行的原子性、可見性以及有序性的問題,它是判斷數據是否存在競爭、線程是否安全的依據
在JMM中若是一個操做中的結果須要對另外一個操做可見,那麼這兩個操做以前必需要存在happens-before關係 (兩個操做能夠是同一個線程也能夠不是一個線程)
規則內容:
注意: 兩個操做之間具備 happens-before 關係,並不意味着前一個操做必需要在後一個操做以前執行,只須要前一個操做的結果對後一個操做可見,而且前一個操做按順序要排在後一個操做以前。
就是前一個操做的結果對後一個操做的結果產生影響,此時編譯器和處理器在處理當前有數據依賴性的操做時不會改變存在數據依賴的兩個操做的執行順序
注意: 此時所說的數據依賴僅僅針對單個處理器中執行的指令序列或者單個線程中執行的操做。不一樣處理器和不一樣線程的狀況編譯器和處理器是不會考慮的
在單線程狀況下無論怎麼重排序程序的執行結果不能被改變,因此若是在單處理器或者單線程的狀況下,編譯器和處理器對於有數據依賴性的操做是不會進行重排序的。反之若是沒有數據依賴性的操做就有可能發生指令重排。
在多線程狀況下才會出現數據競爭
在一個線程中寫了一個變量,在另外一個線程中讀一個變量,並且寫和讀並沒有進行同步
若是在多線程條件下,程序可以正確的使用同步機制,那麼程序的執行將具備順序一致性(就像在單線程條件下執行同樣) 程序最終運行的結果與你預期的結果同樣
4.3.1特性:
4.3.2概念:
在概念上,順序一致性有一個單一的全局內存,在任意時間點最多隻有一個線程能夠鏈接到內存,當在多線程的場景下,會把全部內存的讀寫操做變成串行化
4.3.3案例:
例若有多個併發線程 A B C, A 線程有兩個操做 A1 A2, 他們的執行的順序是 A1 -> A2 。B 線程有三個操做 B1 B2 B3, 他們的執行的順序是 B1 -> B2 ->B3 。C 線程有兩個操做 C1 C2 那麼他們在程序中執行的順序是 C1 -> C2 。
場景分析 :
場景一 : 併發安全(同步)執行順序
A1 -> A2 -> B1 -> B2 ->B3 -> C1 -> C2
場景二: 併發不安全(非同步)執行順序
A1 -> B1 -> A2 -> C1 -> B2 ->B3 -> C2
結論 :
在非同步的場景下,即便三個線程中的每個操做亂序執行,可是在每一個線程中的各自操做仍是保持有序的。而且全部線程都只能看到一個一致的總體執行順序,也就是說三個線程看到的都是該順序 : A1 -> B1 -> A2 -> C1 -> B2 ->B3 -> C2 ,由於順序一致性內存模型中的每一個操做必須當即對任意線程可見。
以上案例場景在JMM中不是這樣的,未同步的程序在JMM中不只總體的執行順序變了,就連每一個線程的看到的操做執行順序也是不同的。
例如前面所說的若是線程A將變量的值 a = 2 寫入到了本身的本地內存中,尚未刷入到主存中,在線程 A 來看值是變了,可是其餘線程 B 線程 C 根本看不到值的改變,就認爲線程A 的操做尚未發生,只有線程 A 將工做內存中的值刷回主內存線程 B和線程C 才能的到。可是若是是同步的狀況下,順序一致性模型和JMM模型執行的結果是一致的,可是程序的執行順序不必定,由於在JMM中,會發生指令重排現象因此執行順序會不一致。