我愛學習網-上傳
當前位置: 主頁 > 文庫 > Java >

理解java內存模型

時間:2020-08-22 16:45來源:我愛學習網 作者:apple 點擊:

前言

在學習java并發編程的過程中,我們通常會遇到一個概念,那就是“java內存模型”。在我之前的很長一段時間里,對于“java內存模型”都是處于一種似懂非懂的朦朧狀態,看似理解了與之相關的緩存一致性,原子性,可見性,有序性,happen-before原則,內存屏障 等一系列概念,但是總是無法把他們串聯起來,還原java內存模型的本來面貌。

 

相關概念定義

首先,在這里,我們先對內存模型做一個總的抽象的定義: 內存模型本質上是對內存訪問(讀取和寫入)的規則、規范、以及協議 的一種抽象,這個抽象分為主要分為兩個層面:1.內存的結構、2.內存的訪問(讀取和寫入)規則

內存模型從不同的層次去看,他可以分為處理器的內存模型、操作系統的內存模型、編程語言的內存模型(如java內存模型)、應用的內存模型(如redis的內存模型)。本質上他們都只是站在不同的角度,對內存的讀寫、管理、結構定義了抽象的規范。

 

處理器內存模型

在談java內存模型之前,有必要先談談處理器的內存模型。因為java內存模型是一種高層次的抽象,本質上它的實現也是建立在物理硬件的內存模型和操作系統的內存模型之上的,適當的了解底層的內存模型的接口,可以幫助我們更好的理解java內存模型。

從個硬件性能的角度講,由于CPU的運算速度遠大于內存的讀寫速度,如果CPU進行運算時,每次都從內存中讀寫數據,那么CPU將會有大量的時間是在等待內存的數據讀寫,這將會導致CPU利用率低下,所以為了最大化CPU的性能和最優化的成本考慮,現代計算機在CPU和內存之間增加了多個高速緩存(L1>L2>L3>Memory),通過將運算需要使用到的數據復制到高速緩存中,當CPU運算時直接從高速緩存讀寫數據,當運算結束后再從緩存把數據同步回內存之中,這樣處理器就無須等待緩慢的內存讀寫了。由此實現了,在成本可控的前提下(如果使用制作高速緩沖區的材料來制作內存,成本過高,經濟效益低下),最大化發揮CPU的性能。

現代常見的多核CPU為多級緩存結構,一般為三級緩存,即L1 cache,L2 cache,L3 cache,存取性能依次遞減,具體可參照下圖。其中L1為CPU core(cpu核心)私有,即每個cpu core都有一個專屬于自己的L1 cache,而L2,L3為多個CPU core共享。

 

雖然這種基于高速緩存的方式,提高了CPU的使用效率,解決了內存讀取速度與CPU運行速度的矛盾,但同時也引入了新的復雜度,那就是緩存一致性問題。簡單的說,緩存一致性問題就是當多個處理器對同一變量進行并發操作的時候,由于每個CPU都有各自的高速緩存,那最后寫入主存的數據該以誰的緩存為準呢?可以想見,如果沒有協議規范這種內存讀寫操作,那么程序在并發運行情況就會充滿不確定性,從而無法正常運行。

 

為了解決上述問題,現代CPU在高速緩存與主內存之間引入了“緩存一致性協議”來解決這個問題,其具體結構如下圖:

 

下面我們簡單的介紹一下比較常見的緩存一致性協議“MESI協議”,它的核心思想就是通過給緩存行(cpu從內存加載數據到高速緩存的基本單位)添加一系列狀態(具體狀態如下表),而后限制CPU對不同狀態的緩存行的讀寫操作,從而達到數據一致性的目的。

狀態

描述

M(Modified)

該行數據被修改,以和該數據在內存中的映像所不同。最新的數據只存在于Cache中

E(Exclusive)

該行數據有效,且數據與內存中的數據一致,但數據值只存在于本Cache中。通俗來說,該數據只在Cache中獨一份

S(Share)

該行數據有效,且該數據與內存中的數據一致。同時,該數據存在與其它多個Cache中

I(Invalid)

該行數據無效

java內存模型

眾所周知java是一門“跨平臺”的編程語言,它的核心設計目標是“一次編譯,到處運行”,然而在java之前的主流編程語言(如c/c++等),對于內存的訪問操作使用的是物理硬件和操作系統的內存模型。因此,會由于不同平臺上內存模型的差異,有可能會導致程序在一套平臺上并發完成正常,而在另外一套平臺上并發訪問卻經常出現錯誤。由上可以看出,直接使用物理硬件和操作系統的內存模型的設計與java語言“一次編譯,到處運行”的設計目標是相違背。因此,java語言的設計者,設計了java內存模型,一組java虛擬機級別的抽象的內存訪問規范。

 

通過上面的介紹,我們可以了解到定義java內存模型的目的,是為了隔離不同硬件和操作系統上內存訪問的差異,給java開發者提供一致的內存訪問模型,從而實現java語言的跨平臺性。某種意義上講java內存模型其本質就是位于java開發者與硬件、操作系統內存模型之間的一組接口,而java內存模型的具體實現則像是一個適配器,使得同一java程序可以在各個平臺上實現安全的并發內存訪問。

從jvm的設計者角度看,定義java內存模型并非是一件容易的事情,一方面它需要足夠嚴謹,這樣才能讓java的并發內存訪問不產生歧義,另一方面,它又需要足夠的寬松,給jvm的實現者留下可能多的優化空間。

 

  • java內存模型定義的是線程如何訪問內存中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出這樣的底層細節(隱含前提,將工作內存視為線程的一部分)。

  • 從線程通訊角度去看,java語言中線程的通訊是通過共享內存來實現的,所以也可以說java內存模型,規定是線程間如何安全通訊的規則。

 

jvm內存操作的字節碼指令

lock(鎖定):作用于主內存變量,它把一個變量表示為一條線程獨占狀態
unlock(解鎖):作用于主內存的變量,它把一個處于鎖定狀態的變量釋放處理,釋放后的變量才可以被其他線程鎖定。
read(讀取):作用于主內存的變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的load動作使用。
load(載入):作用于工作內存的變量,它把read操作從主內存中得到的變量放入工作內存的變量副本中。
use(使用):作用于工作內存的變量,它把工作內存中的一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作。
assign(賦值):作用于工作內存的變量,它把 一個從執行引擎接收到的值賦給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時會執行這個操作。
store(存儲):作用于工作內存的變量,它把工作內存中的一個變量的值傳送到主內存中,以便隨后的writr操作。

Java內存模型規定了在執行上述8種基本操作時必須滿足如下規則:
1. 不允許read和load、store和write操作之一單獨出現,即不允許一個變量從主內存讀取了但工作內存不接受,或者從工作內存發起回寫了但主內存不接受的情況出現。
2. 不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變了之后必須把該變化同步回主內存。
3. 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。
4. 一個新的變量只能在主內存中“誕生”,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量,換句話說,就是對一個變量實施use、store操作之前,必須先執行過了assign和load操作。
5. 一個變量在同一個時刻只允許一條線程對其進行lock操作,但lock操作可以被同一條線程重復執行多次,多次執行lock后,只有執行相同次數的unlock操作,變量才會被解鎖。
6. 如果對一個變量執行lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。
7. 如果一個變量事先沒有被lock操作鎖定,那就不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定住的變量。
8. 對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store、write操作)。

                                                            ? ? --以上內容引用自《深入理解java虛擬機》

 

內存讀寫的亂序優化

如果要把一個變量從主內存復制到工作內存,那就要順序地執行read和load操作,如果要把變量從工作內存同步回主內存,就要順序地執行store和write操作。但是,Java內存模型只要求上述兩個操作必須按順序執行,而沒有保證是連續執行。也就是說,read與load之間、store與write之間是可插入其他指令的,如對主內存中的變量a、b進行訪問時,一種可能出現順序是read a、read b、load b、load a。

 

并發讀寫下的可見性問題

happen-before原則

  • 程序順序規則:一個線程中的每個操作,happens-before于該線程中的任意后續操作。

  • 監視器鎖規則:對一個鎖的解鎖,happens-before于隨后對這個鎖的加鎖。

  • volatile變量規則:對一個volatile域的寫,happens-before于任意后續對這個volatile域的

讀。

  • start()規則:如果線程A執行操作ThreadB.start()(啟動線程B),那么A線程的 ThreadB.start()操作happens-before于線程B中的任意操作。

  • join()規則:如果線程A執行操作ThreadB.join()并成功返回,那么線程B中的任意操作

happens-before于線程A從ThreadB.join()操作成功返回。

  • 傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。

 

happen-before原則,描述的是一種偏序關系,并不是簡單的時序上的先后關系,而是對內存讀寫順序的保證。用個更加易于理解的詞來描述,那就是“可見性”,即A happen-before B 說明的是A操作的操作結果對于B操作可見。

 

 

 

------分隔線----------------------------
    ?分享到??
看看啦
主站蜘蛛池模板: 国产精品乱码一区二区三| 黑巨人与欧美精品一区| 中文字幕精品无码一区二区| 无码国产精品一区二区免费模式| 一区二区三区波多野结衣| 99精品国产一区二区三区2021| 91精品国产一区| 国产精品99精品一区二区三区| 国产自产V一区二区三区C| 国产AV天堂无码一区二区三区| 亚洲一区二区三区免费观看| 成人精品视频一区二区三区不卡| 亚洲欧美日韩一区二区三区在线 | 色妞AV永久一区二区国产AV| 国产精品一区二区综合| 亚洲av乱码中文一区二区三区 | 天堂国产一区二区三区| 国产午夜精品一区理论片飘花| 一区二区在线免费视频| 香蕉久久AⅤ一区二区三区| 国产一区二区三区高清视频| 国产精品一区二区久久精品涩爱| 精品亚洲AV无码一区二区| 亚洲一区二区在线免费观看| 国产精品免费一区二区三区四区| 国产成人无码精品一区在线观看| 色婷婷av一区二区三区仙踪林| 一区二区三区免费视频网站| 日韩伦理一区二区| 日韩AV无码一区二区三区不卡毛片| 97一区二区三区四区久久 | 中文字幕乱码一区久久麻豆樱花 | 日本韩国一区二区三区| 成人精品一区二区三区中文字幕| 精品久久久中文字幕一区| 午夜无码视频一区二区三区| 国精无码欧精品亚洲一区| 人妻夜夜爽天天爽一区| 亚洲一区影音先锋色资源| 国产成人综合精品一区| 国精无码欧精品亚洲一区|