(1). 主內存與工作內存
Java內存模型規定了所有的變量都存儲在主內存中.
每類線程的變量的主內存副本拷貝,線程對變量的所有操作(讀操作,賦值操作等)都必須工作內存中進行,而不能直接讀寫主內存中的變量.
不同的線程之間無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要通過主內存來完成.
(2)內存之間額操作
主內存與工作內存交互,Java內存模型定義了8種操作來完成.
a)lock(鎖定):作用於主內存的變量,它把一個變量標識為一條線性獨占的狀態.
b)ublock(解鎖):作用於主內存的變量,它把一個處於鎖狀態的變量釋放出來,釋放後的變量才可以被其他線程鎖定.
c)read(讀取):作用於主內存的變量,它把一個變量的值從主內存傳輸到線程的內存中,以便隨後的load動作使用.
d)load(載入):作用於內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中.
e)use(使用):作用於工作內存中的變量,它把工作內存中的一個變量的值傳遞給執行引擎,每當虛擬機遇到一個需要使用變量的值的字節碼指令時將會執行這個操作.
f)assign(賦值):作用於工作內存中的變量,它把一個執行引擎接收到的值賦值給工作內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操作.
g)store(存儲):作用於工作內存的變量,它把工作內存中一個變量的值傳遞到主內存中,以便隨後的write操作使用.
h)write(寫入):作用於主內存的變量,它把store操作從工作內存中得到的變量的值放入到主內存的變量中.
(3)Java內存模型規定了在執行上述8種基本操作時必須滿足如下規則:
a) 不允許read和load,store和write操作之一單獨出現,既不允許一個變量從主存讀取了但是工作內存發起回寫不接受的情況出現
b) 不允許一個線程丟棄它的最近的assign操作,即變量在工作內存中改變了之後必須把變量同步到主內存
c) 不允許一個線程無原因(沒有發生過任何assign操作)把數據從線程的工作內存同步會主內存
d) 一個新的變量只能在主內存中“誕生”,不允許在工作內存中直接使用一個未被初始化的變量,換句話說就是 對一個變量實施use,store操作之前,必須執行過了assign和load操作
e) 一個人變量在同一時刻只允許一條線程對其進行lock操作可以被同一條線程重復執行多次,多次執行lock後,只有執行相同德爾unlock操作,變量才會被解鎖
f) 如果一個變量執行lock操作,那將會清空工作內存中此變量的值,在執行引擎使用這個變量前,需要重新執行load或assign操作初始化變量的值
g) 如果一個變量事先沒有被lock操作鎖定,那就不允許對它執行unlock操作,也不允許unlock一個被其他線程鎖定的變量
h) 對一個變量執行unlock操作之前,必須先把此變量同步會煮主內存(執行store,write操作)
(4)對於volatile型變量的特殊規則:
a) 只能保證可見性
b) 禁止指令重排序優化
(5)對long和double型變量的特殊規則:
a) Java內存模型規定:允許虛擬機將沒有被volatile修飾的64位數據的讀寫操作劃分為兩次32位操作進行,及允許虛擬機實現選擇可以不保證64位數據類型的load,store,read和write這4個操作的原子性。這就是long和double的非原子性協定
(6)原子性、可見性與有序性
原子性:由Java內存模型來直接保證的原子性變量操作包括read,load,assign,use,store和write
可見性:指當一個線程修改了共享變量的值,其他線程能夠立即得知這個修改
有序性:如果在本線程內觀察,所有的操作都是有序的,如果在一個線程中觀察另一個線程,所有的操作都是無序的
(7)先行發生原則
先行發生時Java內存模型中定義的兩項操作之間的關系,如果說操作A先行發生於操作B,其實就是在發生操作B之前,操作A產生的影響能夠被操作B觀察到。“影響”包括修改了內存中共享變量的值,發送了消息,調用了方法等。
程序次序規則:在一個線程內,按照程序代碼順序,書寫在前面的操作先於發生在書寫在後面的操作。准確的說,應該是控制流順序而不是程序代碼順序,因為要考慮分支、循環等結構。
管程鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作。
volatile變量規則:對一個volatile變量的寫操作先行發生於後面對這個變量的讀操作,這裡的“後面”是指時間上的先後順序
線程啟動規則:Thread對象的start方法先行發生於此線程的每一個動作
線程終止規則:線程中的所有操作都先於此線程的終止檢測,我們可以通過Thread.join()方法結束。THread.Alive()的返回值等手段檢到線程已經終止執行
線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生,可以通過Thread.interrupted()方法檢測到是否有中斷發生
對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始
傳遞性:如果操作A先行發生於操作B,操作B先行發生於操作C,那就得出操作A先行發生於操作C的結論