從單例模式挖到內存模型(四)----java內存模型

java內存模型:

Java內存模型即Java MemoryModel,簡稱JMM。JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工作方式。

JDK1.5版本對java的內存模型進行了重構,開始使用新的JSR-133內存模型。

JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每個線程都有一個私有的本地內存(local memory)或叫工作內存,本地內存中存儲了該線程以讀/寫共享變量的副本。

注:本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其他的硬件和編譯器優化。



 

線程間的通信:

1. 線程A把本地內存A中更新過的共享變量刷新到主內存中去。

2. 線程B到主內存中去讀取線程A之前已更新過的共享變量。


 

Java 內存模型中的可見性、原子性和有序性

可見性:

可見性是指線程之間的可見性,一個線程修改的狀態對另一個線程是可見的。也就是一個線程修改的結果。另一個線程馬上就能看到。比如:用volatile修飾的變量,就會具有可見性。volatile修飾的變量不允許線程內部緩存和重排序,即直接修改內存。所以對其他線程是可見的。但是這裏需要注意一個問題,volatile只能讓被他修飾內容具有可見性,但不能保證它具有原子性。比如 volatile int a = 0;之後有一個操作 a++;這個變量a具有可見性,但是a++ 依然是一個非原子操作,也就是這個操作同樣存在線程安全問題。

在 Java 中 volatile、synchronized 和 final 實現可見性。

原子性:

原子是世界上的最小單位,具有不可分割性。比如 a=0;(a非long和double類型)這個操作是不可分割的,那麼我們說這個操作時原子操作。再比如:a++;這個操作實際是a = a + 1;是可分割的,所以他不是一個原子操作。非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那麼我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

在 Java 中 synchronized 和在 lock、unlock 中操作保證原子性。volatile不保證原子性。

有序性:

Java 語言提供了 volatile 和synchronized 兩個關鍵字來保證線程之間操作的有序性,volatile 是因爲其本身包含“禁止指令重排序”的語義,synchronized 是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規則獲得的,此規則決定了持有同一個對象鎖的兩個同步塊只能串行執行。

 

java內存模型中的8種基本操作:

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

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

 

Java內存模型還規定了在執行上述八種基本操作時,必須滿足如下規則:

  • 不允許read和load、store和write操作之一單獨出現
  • 不允許一個線程丟棄它的最近assign的操作,即變量在工作內存中改變了之後必須同步到主內存中。
  • 不允許一個線程無原因地(沒有發生過任何assign操作)把數據從工作內存同步回主內存中。
  • 一個新的變量只能在主內存中誕生,不允許在工作內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操作之前,必須先執行過了assign和load操作。
  • 一個變量在同一時刻只允許一條線程對其進行lock操作,lock和unlock必須成對出現
  • 如果對一個變量執行lock操作,將會清空工作內存中此變量的值,在執行引擎使用這個變量前需要重新執行load或assign操作初始化變量的值
  • 如果一個變量事先沒有被lock操作鎖定,則不允許對它執行unlock操作;也不允許去unlock一個被其他線程鎖定的變量。
  • 對一個變量執行unlock操作之前,必須先把此變量同步到主內存中(執行store和write操作)。

內存屏障

Java編譯器在生成指令序列的適當位置會插入內存屏障指令來禁止特定類型的處理器重排序。

Java內存模型把內存屏障分爲LoadLoad、LoadStore、StoreLoad和StoreStore四種:

LoadLoad屏障:對於這樣的語句Load1;LoadLoad; Load2,在Load2及後續讀取操作要讀取的數據被訪問前,保證Load1要讀取的數據被讀取完畢。

StoreStore屏障:對於這樣的語句Store1;StoreStore; Store2,在Store2及後續寫入操作執行前,保證Store1的寫入操作對其它處理器可見。

LoadStore屏障:對於這樣的語句Load1;LoadStore; Store2,在Store2及後續寫入操作被刷出前,保證Load1要讀取的數據被讀取完畢。

StoreLoad屏障:對於這樣的語句Store1;StoreLoad; Load2,在Load2及後續所有讀取操作執行前,保證Store1的寫入對所有處理器可見。它的開銷是四種屏障中最大的。

 

所謂的JSR-133內存模型

從JDK1.5開始,java使用JSR-133內存模型,直到現在的JDK1.8也依然沿用了JDK1.5時候的內存模型,這種內存模型基於happens-before的概念來闡述操作之間的內存可見性,意思是,如果第一個操作的結果對第二個操作可見,那麼這兩個操作就必須存在happens-before的關係,但不要求這兩個操作在一個線程中進行。

happens-before規則如下:

1,程序順序規則:一個線程中的每個操作,happens-before於該線程中任意的後續操作。

2,監視器鎖規則:對一個鎖的解鎖操作,happens-before於隨後對這個鎖的加鎖操作。

3,volatile域規則:對一個volatile域的寫操作,happens-before於任意線程後續對這個volatile域的讀。

4,傳遞性規則:如果 Ahappens-before B,且 B happens-before C,那麼A happens-before C。

注意:兩個操作之間具有happens-before關係,並不意味前一個操作必須要在後一個操作之前執行!僅僅要求前一個操作的執行結果,對於後一個操作是可見的,且前一個操作按順序排在後一個操作之前。

 

JVM對內存模型的實現方式

以上所說的內存模型,通俗一點說就是有一塊內存空間當做主存,共享的變量都放主存裏,線程各自有自己的本地內存,用來緩存各自要用的變量,線程之間通過主存進行數據交互。

JVM對此內存模型的實現方式就是:

1,有一塊內存空間當做主存,叫做堆內存,

2,線程各自有各自的本地內存,叫線程棧,也叫調用棧。

3,線程棧裏包含了當前線程執行的方法調用相關信息,還有當前方法的本地變量信息。

4,各線程只能訪問自己的線程棧,不能訪問其他線程的線程棧。

5,所有原始類型(boolean,byte,short,char,int,long,float,double)的本地變量都直接保存在線程棧當中,各線程之間獨立,但是線程之間可以傳輸原始類型的副本(還是不能算共享)。

6,非原始類型的對象會被存儲到堆中,對這個對象的引用會被存儲到棧中。

7,對象的成員方法中的原始類型會被存儲到棧中。

8,對象的成員變量,包括原始類型和包裝類型,還有static類型的變量,都跟着類本身一起存到堆中。

9,如果某個線程要用對象的原始類型成員變量,會拷貝一份到自己的線程棧中。

10,如果某個線程要用對象的包裝類型變量,會直接訪問堆。


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