Java內存模型簡單講解

       在實際講解併發與高併發之前我們還需要先學習一下Java虛擬機是怎麼解決這些問題的;爲了屏蔽掉硬件以及各種操作系統的內存訪問差異以實現讓java程序在各個平臺下都能達到一致的併發效果,Java虛擬機規範中定義了Java內存模型(Java Memory model,簡稱JMM).個人比較推薦《深入理解JVM》中的關於JMM的講解。很詳細了。書中的理論知識,準備面試足夠了。

1.JMM基本概念

       Java內存模型是一種規範,規範了Java虛擬機與系統內存之間是如何協同工作的,它規定了一個線程如何和何時可以看到其他線程修改過的共享變量的值以及在必要的時候如何同步的訪問共享變量;在明確了Java內存模型是做什麼的之後我們具體的來介紹一下Java內存模型.

2.JMM講解

     首先圖片中內存分配的概念。一個是堆heap,一個是棧stack

      右邊的小圖對應的是cpu和主存的一種信息交互的策略,因爲二者速度相差太大,所以在現代計算機中加入了cache-主存映射機制。具體的在大學計算機組成原理課本上有詳細的講解。

2.1 Java裏的堆-Heap

      這裏需要跟數據結構中的堆做一下區分。不要學混了哈。Java裏面的堆是一個運行的數據區,堆是由垃圾回收來負責的,堆的優勢是可以動態分配內存大小,生命期也不必事先告訴編譯器,因爲她是在運行時動態分配內存的,Java的垃圾回收器會自動的收走這些不再使用的數據,但是堆也有缺點,由於要在運行時動態分配內存,因此對的存取速度會相對來說慢一些。

2.2Java裏的棧-stack

       棧的優勢是存取速度比堆要快,僅次於計算機裏的寄存器,棧的數據是可以共享的;但是它的缺點也比較明顯,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性,棧中主要存在一些基本類型的變量(小寫的int,short,long等基本數據類型)

       JMM要求調用棧和本地變量存放在線程棧上,對象存放在堆上。(注:數組是一個對象)這裏需要具體說一下,一個本地變量也有可能是指向本地的引用,這種情況下引用本地變量是存在線程棧上的,但是對象本身是存放在堆上。一個對象它可能包含方法(上圖中的methodOne和methodTwo)這些方法可能包含本地變量(上圖中的local variable)這些本地變量仍然是存放在線程棧上的,即使這些方法所處的對象存放在堆上。

    不管這個成員變量式原始類型還是引用類型,靜態成員變量跟隨着類的定義一起存放在堆上,堆上的對象可以被所持有這個對象引用的線程訪問,當一個線程可以訪問這個對象的時候,它也可以訪問這個對象的成員變量,如果兩個線程同時訪問堆上的同一個對象,他們將會都訪問這個對象的成員變量,但是每一個線程都擁有了這個對象成員變量的私有拷貝。

3.JMM與計算機硬件的關聯

通過上圖,我們可以看出,JMM與實際的計算機硬件架構有一定差異,硬件架構沒有區分線程棧和堆,因爲對於硬件架構來說,所有的堆和棧都分佈在主內存裏面,部分棧和堆在某些事件可能會出現在CPU緩存和CPU內部的寄存器裏。

3.1JMM抽象架構

線程之間共享變量存儲在主內存裏面,每個線程都有一個私有的本地內存,本地內存是Java內存模型的抽象概念(並不是真實存在的,它包含緩存,寫緩存區,寄存器以及其他的硬件和編譯器的優化),本地內存中它存儲了該線程以讀或寫共享變量拷貝的一個副本,例如上圖中,如果線程A要使用共享變量,它會先拷貝出共享變量的一個副本放在自己的本地內存中,從更低的層次來說,主內存就是硬件的內存,是爲了獲取更好的運行速度,虛擬機以及硬件系統可能會讓工作內存有限存儲於寄存器或高速緩存中。Java內存模型中的工作內存是

寄存器和高速緩存的一個抽象描述,而JVM的靜態內存存儲模型它只是對內存的一種物理劃分,它只侷限在內存侷限在JVM的內存,現在如果線程A和線程B要進行通信必須要經歷兩個步驟:第一步線程A需要將本地內存中更新過的共享變量刷新到主內存中,第二步線程B從主內存中讀取之前線程A刷新到主內存中的共享變量.

下面簡述一個多線程中常見的問題。

①我們假設主內存中當前變量的值爲1,此時線程A和線程B同時開始執行;

②線程A從主內存中拿到的值爲1,然後存儲到自己的本地內存A中最後執行add的操作也就是+1的動作;

③線程A將計算後的結果(此時的結果已經變成2)通過本地內存重新寫回到主內存中;

④線程B此時拿到的值也可能是1,然後將這個值放入自己的本地內存中最後執行加1的操作,最後將計算後的值重新寫入到主內存;

⑤線程A與線程B同時將新的結果寫入到主內存中,而不是有序的交替讀取-計算-寫入;

⑥因爲在程序執行期間兩個線程之間的數據是互相不可見的,因此就計數就出現了問題;

原因已經分析過了,這個時候我們就需要增加一些同步的手段增加程序運行期間併發執行的準確性;

4.JMM中同步的八種操作

①lock(鎖定) : 作用於主內存的變量,把一個變量標識爲一條線程獨佔的狀態;

②unlock(解鎖) : 作用於主內存的變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量可以被其他線程鎖定;

③read(讀取) : 作用於主內存的變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用;

④load(載入) : 作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中;

⑤use(使用) : 作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎;

⑥assign(賦值) : 作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量;

⑦store(存儲) : 作用於工作內存的變量,把工作內存中的一個變量值傳送到主內存中,以便隨後的write的操作;

⑧write(寫入) : 作用於主內存的變量,它把store操作從工作內存中一個變量的值傳送到主內存的變量中;

5.JMM同步規則

①不允許read和load、store和write單獨出現。即不允許一個變量從主內存讀取了但工作內存不接受,或者從工作內從發起會寫了但主內存不接受的情況出現。

②不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變了之後必須把該變化同步到主內存裏。

③不允許一個線程無原因地把數據從線程工作內存同步回主內存中,即沒有發生過任何的assign操作就同步到主內存中。

④一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個沒有被初始化(load或assign)的變量,換句話說,就是對一個變量實施use、store操作之前,必須先執行assign和load操作。

⑤一個變量在同一時刻,只允許一個線程對其進行loack操作,但lock操作可以被同一個線程重複執行多次,多次執行lock後,只有執行相同次數的unloack操作,變量才能被解鎖。

⑥如果對一個變量執行了lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值。

⑦如果一個變量事先沒有被lock操作鎖定,那就不允許對他執行unlock操作,也不允許去unlock一個被其他線程鎖定住的變量

⑧對一個變量執行unlock操作之前,必須先把此變量同步回主內存中(執行store、write操作)。

圖解:

總結:1.JMM是一個規範,它規定了一個線程如何和何時可以看到由其他線程修改過後的共享變量的值以及在必要時如何同步的訪問共享變量,它要求調用棧和本地變量存放在線程棧上,對象存放在堆上。

2.線程間的通信必須要經過主內存。

3.頂一個了同步的八種操作和八種基本規則。(具體參考《深入理解JVM》)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章