硬件內存架構:
一級緩存和二級緩存:一級緩存在CPU,二級在主板或CPU,一些高端CPU還有三級緩存
主內存比L2緩存慢,L2緩存比L1緩存慢,因此,L2緩存命中失敗通常比L1緩存命中失敗的成本高。
每個 CPU 在某一時刻運行一個線程是沒有問題的。這意味着,如果你的 Java 程序是多線程的,在你的 Java 程序中每個 CPU 上一個線程可能同時(並發)執行。
每個 CPU 都包含一系列的寄存器,它們是 CPU 內內存的基礎。CPU 在寄存器上執行操作的速度遠大於在主存上執行的速度。這是因為 CPU 訪問寄存器的速度遠大於主存。所以現代計算機系統都不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內存與處理器之間的緩沖:將運算需要使用到的數據復制到緩存中,讓運算能快速進行,當運算結束后再從緩存同步回內存之中,這樣處理器就無須等待緩慢的內存讀寫了。CPU訪問緩存層的速度快於訪問主存的速度,但通常比訪問內部寄存器的速度還要慢一點。
硬件存在緩存一致性問題:
當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致的情況,如果真的發生這種情況,那同步回到主內存時以誰的緩存數據為准呢?
解決辦法——為了解決一致性的問題,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議來進行操作,這類協議有MSI、MESI(IllinoisProtocol)、MOSI、Synapse、Firefly及DragonProtocol,等等:
指令重排序問題:
為了使得處理器內部的運算單元能盡量被充分利用,處理器可能會對輸入代碼進行亂序執行(Out-Of-Order Execution)優化,處理器會在計算之后將亂序執行的結果重組,保證該結果與順序執行的結果是一致的,但並不保證程序中各個語句計算的先后順序與輸入代碼中的順序一致。
與處理器的亂序執行優化類似,Java虛擬機的即時編譯器中也有類似的指令重排序(Instruction Reorder)優化
volatile關鍵字本身就包含了禁止指令重排序的語義,synchronized規則也保證了串行
軟件中內存模型:
JMM
JMM 的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。此處的變量與 Java 編程中的變量有所區別,它包括了實例字段、靜態字段和構成數組對象的元素,但不包括局部變量與方法參數,因為后者是線程私有的,不會被共享,自然就不會存在競爭問題。
JMM 是圍繞着在並發過程中如何處理原子性、可見性和有序性這 3 個特征來建立的。
存放在堆上的對象可以被所有持有對這個對象引用的線程訪問。當一個線程可以訪問一個對象時,它也可以訪問這個對象的成員變量。如果兩個線程同時調用同一個對象上的同一個方法,它們將會都訪問這個對象的成員變量,但是每一個線程都擁有這個本地變量的私有拷貝。
線程間通信必須要經過主內存
如下,如果線程A與線程B之間要通信的話,必須要經歷下面2個步驟:
1)線程A把本地內存A中更新過的共享變量刷新到主內存中去。
2)線程B到主內存中去讀取線程A之前已更新過的共享變量。
對於硬件,所有的線程棧和堆都分布在主內中。部分線程棧和堆可能有時候【正在執行時】會出現在 CPU 緩存中和 CPU 內部的寄存器中。
當對象和變量被存放在計算機中各種不同的內存區域中時,就可能會出現一些具體的問題。主要包括如下兩個方面:
- 線程對共享變量修改的可見性
- 當讀,寫和檢查共享變量時出現 race conditions【競態條件】
競態條件指:當一個對象或者一個不同步的共享狀態,被兩個或者兩個以上的線程修改時,對訪問順序敏感,則會產生競態條件。
Happens-Before
JMM 為程序中所有的操作定義了一個偏序關系,稱之為 Happens-Before。也就是保證線程可見性的操作規則。
▲鎖的內存語義:
- 當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新到主內存中
- 當線程獲取鎖時,JMM會把該線程對應的本地內存置為無效。從而使得被監視器保護的臨界區代碼必須從主內存中讀取共享變量
總結如下圖:
參考鏈接:https://zhuanlan.zhihu.com/p/29881777