1. 程式人生 > >轉: 【Java並發編程】之十七:深入Java內存模型—內存操作規則總結

轉: 【Java並發編程】之十七:深入Java內存模型—內存操作規則總結

tle 沒有 article 類型 javase 感知 執行引擎 要求 lock

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/17377197


主內存與工作內存

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


內存間交互操作


Java內存模型中定義了以下8中操作來完成主內存與工作內存之間交互的實現細節:


1、luck(鎖定):作用於主內存的變量,它把一個變量標示為一條線程獨占的狀態。

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

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

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

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

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

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

8、write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量值放入主內存的變量中。


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


1、不允許read和load、store和write操作之一單獨出現,以上兩個操作必須按順序執行,但沒有保證必須連續執行,也就是說,read與load之間、store與write之間是可插入其他指令的。

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

3、不允許一個線程無原因地(沒有發生過任何assign操作)把數據從線程的工作內存同步回主內存中。

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

5、一個變量在同一個時刻只允許一條線程對其執行lock操作,但lock操作可以被同一個條線程重復執行多次,多次執行lock後,只有執行相同次數的unlock操作,變量才會被解鎖。

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

7、如果一個變量實現沒有被lock操作鎖定,則不允許對它執行unlock操作,也不允許去unlock一個被其他線程鎖定的變量。

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


volatile型變量的特殊規則


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

1、保證此變量對所有線程的可見性。這裏不具體解釋了。需要註意,volatile變量的寫操作除了對它本身的讀操作可見外,volatile寫操作之前的所有共享變量均對volatile讀操作之後的操作可見,另外註意其適用場景,詳見http://blog.csdn.net/ns_code/article/details/17290021http://blog.csdn.net/ns_code/article/details/17101369這兩篇博文。

2、禁止指令重排序優化。普通的變量僅僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能獲得正確的結果,而不能保證變量賦值操作的順序與程序中的執行順序一致,在單線程中,我們是無法感知這一點的。


補充: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/vking_wang/article/details/8574376#t2

《深入理解Java虛擬機——JVM高級特性與最佳實踐》第12章

轉: 【Java並發編程】之十七:深入Java內存模型—內存操作規則總結