Java內存模型

Java內存模型

Java虛擬機規範中試圖定義一種Java內存模型來屏蔽掉各類硬件和操做系統的內存訪問差別,以實現讓Java程序在各類平臺下都能達到一致的內存訪問效果。
Java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。
Java內存模型規定了全部的變量都存儲在主內存中,每條線程還有本身的工做內存,線程的工做內存中保存了被該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值的傳遞均須要經過主內存來完成。java

內存間交互操做

關於主內存與工做內存之間具體的交互協議(即一個變量如何從主內存拷貝到工做內存、如何從工做內存同步回主內存之類的實現細節),Java內存模型中定義瞭如下8種操做來完成,虛擬機實現時必須保證下面說起的每一種操做都是原子的、不可再分的。安全

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

Java內存模型還規定了在執行上述8種基本操做時必須知足以下規則:併發

  • 不容許read和load、store和write操做之一單獨出現,即不容許一個變量從主內存讀取了但工做內存不接受,或者從共工做內存發起回寫了但主內存不接受的狀況。
  • 不容許一個線程丟棄它的最近的assign操做,即變量在工做內存中改變了以後必須把該變化同步回主內存。
  • 不容許一個線程無緣由地(沒有發生任何assign操做)把數據從線程的工做內存同步回主內存中。
  • 一個新的變量只能在主內存中「誕生」,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量,換句話說,就是對一個變量實施use、store操做以前,必須先執行過了assign和load操做。
  • 一個變量在同一個時刻只容許一條線程對其進行lock操做,但lock操做能夠被同一條線程重複執行屢次,屢次執行lock後,只有執行相同次數的unlock操做,變量纔會被解鎖。
  • 若是對一個變量執行lock操做,那將會清空工做內存中此變量的值,在執行引擎使用這個變量前,須要從新執行load或assign操做初始化變量的值。
  • 若是一個變量事先沒有被lock操做鎖定,那就不容許對它執行unlock操做,也不容許去unlock一個被其它線程鎖定住的變量。
  • 對一個變量執行unlock操做以前,必須先把此變量同步會主內存(執行store、write操做)。

這8種內存訪問操做以及上述規則限定,再加上稍後介紹的對volatile的一些特定規定,就已經徹底肯定了Java程序中哪些內存訪問操做在併發下是安全的。因爲這種定義至關嚴謹但有十分煩瑣,實踐起來和麻煩,咱們可使用他的一個等效判斷原則-先行發生原則,用來肯定一個訪問在併發環境下是否安全。app

volatile型變量的特殊規則

關鍵字volatile能夠說是Java虛擬機提供的最輕量級的同步機制,當一個變量定義爲volatile以後,它將具有兩種特性。優化

  • 保證此變量對全部線程的可見性,這裏的「可見性」是指當一條線程修改了這個變量的值,新值對於其它線程來講是能夠當即得知的。
  • 禁止指令重排序優化。普通的變量僅僅會保證在該方法的執行過程當中全部依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操做的順序與程序代碼中的執行順序一致。而volatile關鍵字則能夠避免此類狀況的發生。

因爲volatile變量只能保證可見性,在不符合如下兩條規則的運算場景中,咱們仍然要經過加鎖(使用synchronized或java.util.concurrent中的原子類)來保證原子性。編碼

  • 運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值。
  • 變量不須要與其它的狀態變量共同參與不變約束。

先行發生原則

若是Java內存模型中全部的有序性都僅僅靠volatile和synchronized來完成,那麼有一些操做將會變得很煩瑣,可是咱們在編寫Java併發代碼的時候並無感受到這一點,這是由於Java語言中有一個「先行發生」(happens-before)的原則。它是判斷數據是否存在競爭、線程是否安全的依據,依賴這個原則,咱們能夠經過幾條規則一攬子地解決併發環境下兩個操做之間是否可能存在衝突的全部問題。操作系統

下面是Java內存模型下一些「自然的」先行發生關係,這些先行發生關係無須任何同步器協助就已經存在,能夠在編碼中直接使用。若是兩個操做之間的關係不在此列,而且沒法從下列規則推導出來的話,它們就沒有順序性保障,虛擬機能夠對它們隨意地進行重排序。線程

  • 程序次序規則(Program Order Rule):在一個線程內,按照程序代碼順序,書寫在前面的操做先行發生於書寫在後面的操做。
  • 管程鎖定規則(Monitor Lock Rule):一個unlock操做先行發生於後面對同一個鎖的lock操做。這裏必須強調的是同一個鎖,而「後面」是指時間上的前後順序。
  • volatile變量規則(Volatile Variable Rule):對一個volatile變量的寫操做先行發生於後面對這個變量的讀操做,這裏的「後面」一樣是指時間上的前後順序。
  • 線程啓動規則(Thread Start Rule):Thread對象的start()方法先行發生於此線程的每個動做。
  • 線程終止規則(Thread Termination Rule):線程中的全部操做都先行發生於對此線程的終止檢測,咱們能夠經過Thread.join()方法結束、Thread.isAlive()的返回值等手段檢測到線程已經終止執行。
  • 線程中斷規則(Thread Interruption Rule):對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,能夠經過Thread.interrupted()方法檢測到是否有中斷髮生。
  • 對象終結規則(Finalizer Rule):一個對象的初始化完成先行發生於它的finalize()方法的開始。
  • 傳遞性(Transitivity):若是A操做先行發生於B操做,B操做先行發生與C操做,那就能夠得出操做A先行發生於操做C。
相關文章
相關標籤/搜索