深入理解Java記憶體模型 【1】CPU和緩存的一致性 我們應該都知道,電腦在執行程式的時候,每條指令都是在CPU中執行的,而執行的時候,又免不了要和數據打交道。而電腦上面的數據,是存放在主存當中的,也就是電腦的物理記憶體啦。 剛開始,還相安無事的,但是隨著CPU技術的發展,CPU的執行速 ...
深入理解Java記憶體模型
【1】CPU和緩存的一致性
我們應該都知道,電腦在執行程式的時候,每條指令都是在CPU中執行的,而執行的時候,又免不了要和數據打交道。而電腦上面的數據,是存放在主存當中的,也就是電腦的物理記憶體啦。
剛開始,還相安無事的,但是隨著CPU技術的發展,CPU的執行速度越來越快。而由於記憶體的技術並沒有太大的變化,所以從記憶體中讀取和寫入數據的過程和CPU的執行速度比起來差距就會越來越大,這就導致CPU每次操作記憶體都要耗費很多等待時間。
所以,人們想出來了一個好的辦法,就是在CPU和記憶體之間增加高速緩存。緩存的概念大家都知道,就是保存一份數據拷貝。他的特點是速度快,記憶體小,並且昂貴。
那麼,程式的執行過程就變成了:
當程式在運行過程中,會將運算需要的數據從主存複製一份到CPU的高速緩存當中,那麼CPU進行計算時就可以直接從它的高速緩存讀取數據和向其中寫入數據,當運算結束之後,再將高速緩存中的數據刷新到主存當中。
在CPU和主存之間增加緩存,在多線程場景下就可能存在緩存一致性問題,也就是說,在多核CPU中,每個核的自己的緩存中,關於同一個數據的緩存內容可能不一致。
【2】處理器優化和指令重排
上面提到在在CPU和主存之間增加緩存,在多線程場景下會存在緩存一致性問題。除了這種情況,還有一種硬體問題也比較重要。那就是為了使處理器內部的運算單元能夠儘量的被充分利用,處理器可能會對輸入代碼進行亂序執行處理。這就是處理器優化。
除了現在很多流行的處理器會對代碼進行優化亂序處理,很多編程語言的編譯器也會有類似的優化,比如Java虛擬機的即時編譯器(JIT)也會做指令重排。
可想而知,如果任由處理器優化和編譯器對指令重排的話,就可能導致各種各樣的問題。
解決方法:記憶體屏障
【3】什麼是Java記憶體模型
記憶體模型
為了保證共用記憶體的正確性(可見性、有序性、原子性),記憶體模型定義了共用記憶體系統中多線程程式讀寫操作行為的規範。通過這些規則來規範對記憶體的讀寫操作,從而保證指令執行的正確性。
Java記憶體模型(Java Memory Model,JMM)是java虛擬機規範定義的,用來屏蔽掉java程式在各種不同的硬體和操作系統對記憶體的訪問的差異,這樣就可以實現java程式在各種不同的平臺上都能達到記憶體訪問的一致性。
Java記憶體模型的主要目標是定義程式中變數的訪問規則。即在虛擬機中將變數存儲到主記憶體或者將變數從主記憶體取出這樣的底層細節。需要註意的是這裡的變數跟我們寫java程式中的變數不是完全等同的。這裡的變數是指實例欄位,靜態欄位,構成數組對象的元素,但是不包括局部變數和方法參數(因為這是線程私有的)。
Java記憶體模型中涉及到的概念有:
- 主記憶體:java虛擬機規定所有的變數(不是程式中的變數)都必須在主記憶體中產生。可以與前面說的物理機的主記憶體相比,只不過物理機的主記憶體是整個機器的記憶體,而虛擬機的主記憶體是虛擬機記憶體中的一部分。
- 工作記憶體:java虛擬機中每個線程都有自己的工作記憶體,該記憶體是線程私有的。可以與前面說的高速緩存相比。線程的工作記憶體保存了線程需要的變數在主記憶體中的副本。虛擬機規定,線程對主記憶體變數的修改必須線上程的工作記憶體中進行,不能直接讀寫主記憶體中的變數。不同的線程之間也不能相互訪問對方的工作記憶體。如果線程之間需要傳遞變數的值,必須通過主記憶體來作為中介進行傳遞。
工作記憶體和主記憶體的劃分和 Java 堆,棧,方法區的劃分不同,兩者基本沒有關係,如果勉強對應,則主記憶體可理 解為堆中實例數據部分,工作記憶體則對應棧中部分區域
關於Java 堆,棧,方法區:點擊學習
【4】volatile的記憶體語義
volatile寫的記憶體語義:
當寫一個變數的時候,JMM會把該線程的私有記憶體中的共用變數值更新到主記憶體中,並將其他線程中的值置為無效的;
volatile讀的記憶體語義:
當讀一個變數的時候,JMM會先判斷是否私有空間內的值是否失效,若失效,線程接下來會從主存中讀取變數。
詳情見:點擊學習
【5】鎖的記憶體語義
當線程釋放鎖時,JMM會把該線程對應的本地記憶體中的共用變數刷新到主記憶體中。
當線程獲取鎖時,JMM會把該線程對應的本地記憶體置為無效。從而使得被監視器保護的臨界區代碼必須要從主記憶體中去讀取共用變數。
詳情見:點擊學習