理解JVM之java內存模型

  java虛擬機規範中試圖定義一種java內存模型(JMM)來屏蔽掉各類硬件和操做系統內存訪問差別,以實現讓java程序在各類平臺都能打到一致的內存訪問效果.因此java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層實現細節.注意,這裏的變量是包括了實例字段,衆泰字段,構成數組對象的元素,但不包括局部變量和方法參數,由於後者是線程私有的,不會被共享.java

1.主內存和工做內存數組

  JMM規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存.工做內存中保存了該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存的變量,工做內存是線程私有的.這裏指的主內存和工做內存,和java的虛擬機的內存劃分不是同一個層次的劃分,二者不對等,若是非要對等起來,主內存應該對於java堆中對象的數據區域,工做內存對於線程私有的虛擬機棧的一部分.安全

2.主內存與工做內存的交互ide

  主內存與工做內存之間如何交互,java內存模型定義瞭如下8個操做,每一個操做都是原子的,不可再分的(對於double,long可能有例外,其在有些平臺上會以32位爲單位讀取)函數

  1) lock:做用於主內存中的變量,它將一個變量標誌爲一個線程獨佔的狀態。 spa

  2) unlock:做用於主內存中的變量,解除變量的鎖定狀態,被解除鎖定狀態的變量才能被其餘線程鎖定。 操作系統

  3) read:做用於主內存中的變量,它把一個變量的值從主內存中傳遞到工做內存,以便進行下一步的load操做。 線程

  4) load:做用於工做內存中的變量,它把read操做傳遞來的變量值放到工做內存中的變量副本中。 code

  5) use:做用於工做內存中的變量,這個操做把變量副本中的值傳遞給執行引擎。當執行須要使用到變量值的字節碼指令的時候就會執行這個操做。 對象

  6) assign:做用於工做內存中的變量,接收執行引擎傳遞過來的值,將其賦給工做內存中的變量。當執行賦值的字節碼指令的時候就會執行這個操做。

  7) store:做用於工做內存中的變量,它把工做內存中的值傳遞到主內存中來,以便進行下一步write操做。 

  8) write:做用於主內存中的變量,它把store傳遞過來的值放到主內存的變量中。

  在將變量從主內存讀取到工做內存中,必須順序執行read、load;要將變量從工做內存同步回主內存中,必須順序執行store、write。而且這8種操做必須遵循如下規則: 
  1) 不容許read和load、store和write操做之一單獨出現。即不容許一個變量從主內存被讀取了,可是工做內存不接受,或者從工做內存回寫了可是主內存不接受。 

  2) 不容許一個線程丟棄它最近的一個assign操做,即變量在工做內存被更改後必須同步改更改回主內存。 

  3) 工做內存中的變量在沒有執行過assign操做時,不容許無心義的同步回主內存。 

  4) 在執行use前必須已執行load,在執行store前必須已執行assign。 

  5) 一個變量在同一時刻只容許一個線程對其執行lock操做,一個線程能夠對同一個變量執行屢次lock,但必須執行相同次數的unlock操做纔可解鎖。 

  6) 一個線程在lock一個變量的時候,將會清空工做內存中的此變量的值,執行引擎在use前必須從新read和load。 

  7) 線程不容許unlock其餘線程的lock操做。而且unlock操做必須是在本線程的lock操做以後。 

  8) 在執行unlock以前,必須首先執行了store和write操做。

  對於普通變量來講,他的讀寫操做以下:

  變量讀:

  1) 進入同步塊時,同步塊中讀變量的值時,將會從主存中刷新到工做區,讀到最新值
  2) 讀volatile變量時,將會從主存中刷新到工做區,讀到最新值;
  3) 讀普通變量時,若是跟隨在讀volatile變量以後,將會從主存刷新到工做區,讀到最新值
  4) 讀final變量時,若是未初始化完成,則將等待final變量完成初始化,並獲取主存中的最新值
  5) 新建線程中,變量的值將會從主存刷新到工做區,讀到最新值
  變量寫:
  1) 退出同步塊時,同步塊中變量的賦值被強制刷新到主存;
  2) volatile變量的賦值,被強制刷新到主存,時機在讀volatile變量以前;
  3) 普通變量隨同volatile變量,在同一個線程中的賦值,將跟隨volatile變量被刷新到主存;
  4) final變量的賦值,在讀final變量以前將被強制刷新到主存;
  5) 單獨的普通變量的賦值,將在線程結束以前被刷新到主存;

3.volatitle變量的特殊規則

  volatile是java虛擬機提供的最輕量級的同步機制,volatile變量具備兩個特性:一個是保證此變量對全部線程的可見性,即若是該變量的值被修改,這個新值對於其餘線程來講是當即得知的.另外一個是volatile會阻止指令重排序.因爲volatile變量第一個特性,致使不少人認爲volatile變量的是絕對的線程安全的,這個想法是有問題的,好比下面這個代碼

 1 private static volatile int a=0;  2     
 3     private static void increase(){  4         a++;  5  }  6     
 7     public static void main(String[] args) {  8         Thread[] threads=new Thread[20];  9         for(int i=0;i<20;i++){ 10             threads[i]=new Thread(new Runnable() { 11  @Override 12                 public void run() { 13                     for(int i=0;i<10000;i++) 14  increase(); 15  } 16  }); 17  threads[i].start(); 18  } 19         while(Thread.activeCount()>1) 20  Thread.yield(); 21  System.out.println(a); 22     }

  這裏開啓了20個線程,每一個線程對a變量執行10000次加一操做,若是volatile變量是絕對的線程安全的,那麼這個程序輸出的結果必然爲200000,可是這個程序輸出的結果基本上是低於200000的.緣由是,實現volatile變量的可見性的方法其實是線程每次讀這個變量的時候都會從主內存中讀取這個值,每次寫這個變量時寫完會強制刷新到主內存中.在一個線程讀取了這個值以後,另外一個線程修改了主內存中的值,那麼前一個線程的值就變成了過時的值,因此結果會出現偏差.

  因爲volatile變量只能保證可見性,因此在一些狀況下仍是須要加鎖來保證原子性,在如下狀況下可以不加鎖使用volatile變量來保證原子性:

  1) 運算結果並不依賴變量當前的值,或者確保只有單個線程能夠更新變量的值.

  2) 變量不須要與其餘的狀態變量共同參與不變約束

4.java內存模型的特徵

  1) 原子性:java內存模型直接保證原子性變量操做的包括read,load,assign,use,store,write,大體的能夠認爲基本數據類型的讀寫是具有原子性的(double跟long除外,開頭已經說了)

實現大範圍的原子性可以使用lock與unlock操做.

  2) 可見性:當一個線程修改了共享變量的值其餘線程可以當即得知這個修改,變量如何實現可見性前面已經提到.

  3) 有序性:java程序中自然的有序性能夠總結爲:若是本線程內觀察,全部操做都是有序的,若是一個線程中觀察另外一個線程,全部操做都是無序的.

5.先行先發生原則

  先行發生原則--是判斷是否存在數據競爭、線程是否安全的主要依據。

  先行發生是Java內存模型中定義的兩項操做之間的偏序關係。若是說操做A先行發生於操做B,其實就是說在發生操做B以前,操做A產生的影響被操做B察覺。

  下面是JAVA內存模型下一些自然的先行發生關係,不須要熱河同步器協助就已經存在,虛擬機能夠對他門進行重排序.

  一、程序次序規則:在一個線程內,書寫在前面的代碼先行發生於後面的。確切地說應該是,按照程序的控制流順序,由於存在一些分支結構。

  二、Volatile變量規則:對一個volatile修飾的變量,對他的寫操做先行發生於讀操做.

  三、線程啓動規則:Thread對象的start()方法先行發生於此線程的每個動做.

  四、線程終止規則:線程的全部操做都先行發生於對此線程的終止檢測.

  五、線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼所檢測到的中斷事件.

  六、對象終止規則:一個對象的初始化完成(構造函數之行結束)先行發生於發的finilize()方法的開始.

  七、傳遞性:A先行發生B,B先行發生C,那麼,A先行發生C.

  八、管程鎖定規則:一個unlock操做先行發生於後面對同一個鎖的lock操做.

相關文章
相關標籤/搜索