【Java併發編程】:深刻Java內存模型—內存操做規則總結

主內存與工做內存

    java內存模型的主要目標是定義程序中各個變量的訪問規則,即在虛擬機中將變量存儲到內存和從內存中取出變量這樣的底層細節。此處的變量主要是指共享變量,存在競爭問題的變量。Java內存模型規定全部的變量都存儲在主內存中,而每條線程還有本身的工做內存,線程的工做內存中保存了該線程使用到的變量的主內存副本拷貝,線程對變量的全部操做(讀取、賦值等)都必須在工做內存中進行,而不能直接讀寫主內存中的變量(根據Java虛擬機規範的規定,volatile變量依然有共享內存的拷貝,可是因爲它特殊的操做順序性規定——從工做內存中讀寫數據前,必須先將主內存中的數據同步到工做內存中,全部看起來如同直接在主內存中讀寫訪問通常,所以這裏的描述對於volatile也不例外)。不一樣線程之間也沒法直接訪問對方工做內存中的變量,線程間變量值得傳遞均須要經過主內存來完成。java

內存間交互操做

    Java內存模型中定義瞭如下8中操做來完成主內存與工做內存之間交互的實現細節:緩存

    一、luck(鎖定):做用於主內存的變量,它把一個變量標示爲一條線程獨佔的狀態。安全

    二、unlock(解鎖):做用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。性能

    三、read(讀取):做用於主內存的變量,它把一個變量的值從主內存傳輸到工做內存中,以便隨後的load動做使用。優化

    四、load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。this

    五、use(使用):做用於工做內存的變量,它把工做內存中的一個變量的值傳遞給執行引擎,每當虛擬機遇到一個須要使用到變量的值得字節碼指令時將會執行這個操做。編碼

    六、assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。spa

    七、store(存儲):做用於工做內存的變量,它把工做內存中的一個變量的值傳遞到主內存中,以便隨後的write操做使用。.net

    八、write(寫入):做用於主內存的變量,它把store操做從工做內存中獲得的變量值放入主內存的變量中。線程

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

    一、不容許read和load、store和write操做之一單獨出現,以上兩個操做必須按順序執行,但沒有保證必須連續執行,也就是說,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操做)。

volatile型變量的特殊規則

    Java內存模型對volatile專門定義了一些特殊的訪問規則,當一個變量被定義成volatile以後,他將具有兩種特性:

    一、保證此變量對全部線程的可見性。這裏不具體解釋了。須要注意,volatile變量的寫操做除了對它自己的讀操做可見外,volatile寫操做以前的全部共享變量均對volatile讀操做以後的操做可見,另外注意其適用場景,詳見http://blog.csdn.net/ns_code/article/details/17290021http://blog.csdn.net/ns_code/article/details/17101369這兩篇博文。

    二、禁止指令重排序優化。普通的變量僅僅會保證在該方法的執行過程當中全部依賴賦值結果的地方都能得到正確的結果,而不能保證變量賦值操做的順序與程序中的執行順序一致,在單線程中,咱們是沒法感知這一點的。

    補充:Java語言規範規定了JVM線程內部維持順序化語義,也就是說只要程序的最終結果等同於它在嚴格的順序化環境下的結果,那麼指令的執行順序就可能與代碼的順序不一致,這個過程經過叫作指令的重排序。指令重排序存在的意義在於:JVM可以根據處理器的特性(CPU的多級緩存系統、多核處理器等)適當的從新排序機器指令,使機器指令更符合CPU的執行特色,最大限度的發揮機器的性能。在沒有同步的狀況下,編譯器、處理器以及運行時等均可能對操做的執行順序進行一些意想不到的調整

final域

    final類型的域是不能修改的,除了這一點外,在Java內存模型中,final域還有着特殊的語義,final域能確保初始化過程的安全性,從而能夠不受限制地訪問不可變對象,並在共享這些對象時無須同步。具體而言,就是被final修飾的字段在構造器中一旦被初始化完成,而且構造器沒有把「this」的引用傳遞出去(this引用逃逸是一件很危險的事情,其餘線程有可能經過這個引用訪問到「初始化了一半」的對象),那麼在其餘線程中就能看到final字段的值,並且其外、外部可見狀態永遠也不會改變。它所帶來的安全性是最簡單最純粹的。

long和double型變量的特殊規則

    Java內存模型要求lock、unlock、read、load、assign、use、store和write這8個操做都具備原子性,可是對於64位的數據類型long和double,在模型中特別定義了一條寬鬆的規定:容許虛擬機將沒有被volatile修飾的64位數據的讀寫操做劃分爲兩次32位的操做來進行。這樣,若是有多個線程共享一個未被聲明爲volatile的long或double類型的變量,而且同時對它們進行讀取和修改操做,那麼某些線程可能會讀到一個既非原值,也非其餘線程修改值得表明了「半個變量」的數值。不過這種讀取到「半個變量」的狀況很是罕見,由於Java內存模型雖然容許虛擬機不把long和double變量的讀寫實現成原子操做,但容許迅疾選擇把這些操做實現爲具備原子性的操做,並且還「強烈建議」虛擬機這樣實現。目前各類平臺下的商用虛擬機幾乎都選擇吧64位數據的讀寫操做做爲原子操做來對待,所以在編碼時,不須要將long和double變量專門聲明爲volatile。

轉載:http://blog.csdn.net/ns_code/article/details/17377197

相關文章
相關標籤/搜索