本文參考 《深入理解JAVA虛擬機(jī)》(周志明) 第二版,p362 万俗,對(duì)文中內(nèi)容提煉加以總結(jié)而成璃吧。
- java內(nèi)存模型的產(chǎn)生,是為了解決各個(gè)硬件和操作系統(tǒng)對(duì)內(nèi)存訪問的差異俯树。
- 主要針對(duì)變量來定義各種規(guī)則帘腹,線程私有變量不算,因?yàn)椴淮嬖诰€程安全問題
- “二八原則”许饿,二指兩個(gè)內(nèi)存阳欲,線程工作內(nèi)存、 主內(nèi)存陋率。線程工作內(nèi)存無法直接訪問(讀寫)主內(nèi)存球化,每個(gè)線程的線程工作內(nèi)存也無法直接互相訪問。這兩個(gè)內(nèi)存的交互有具體協(xié)議瓦糟,即八個(gè)原子操作
-
lock: 作用于主內(nèi)存筒愚,把一個(gè)變量表示為一條線程獨(dú)占
unlock: 作用于主內(nèi)存,把一個(gè)處于鎖定的變量釋放狸页,釋放后才可以被其他線程鎖定
read:作用于主內(nèi)存的變量锨能,把主內(nèi)存變量值傳入工作內(nèi)存,方便后面 load
load:作用于工作內(nèi)存芍耘,將read讀取的值載入工作內(nèi)存副本
use:作用于工作哦內(nèi)存址遇,把工作內(nèi)存的某個(gè)值傳給執(zhí)行引擎,虛擬機(jī)每次使用變量值則調(diào)用這個(gè)
assign:作用于工作內(nèi)存的變量斋竞,把一個(gè)從執(zhí)行引擎接收到的值復(fù)制給工作內(nèi)存的變量倔约,虛擬機(jī)每次給變量復(fù)制使用這個(gè)
store:作用于工作內(nèi)存的變量,把工作內(nèi)存值傳給主內(nèi)存 (類比read)
write:作用于主內(nèi)存的變量坝初,把store操作從工作內(nèi)存中得到的變量的值放入主內(nèi)存中(類比load)
這8個(gè)原子操作的一些規(guī)定 和 后面 volatile的某些特殊約定浸剩,共同決定了java線程安全體系钾军。
1. (read load) (store write) 這兩組操作必須按順序執(zhí)行
2. (read load) (store write)這兩組操作必須完整,不能只出現(xiàn)read 沒有l(wèi)oad 類似這種
3. 如果執(zhí)行了 assign绢要, 則后續(xù)必須有 store write
4. 不允許無原因的(沒發(fā)生過 assign)吏恭,就發(fā)生 store write
5. 新變量只能在主內(nèi)存中誕生
6. lock可以被一個(gè)線程執(zhí)行多次,但unlock需要相同的次數(shù)
7. lock會(huì)清空工作內(nèi)存中此變量的值重罪,在執(zhí)行引擎使用這個(gè)變量前樱哼,需要重新執(zhí)行 load 或 assign
8. 沒有l(wèi)ock 不可 unlock
9. unlock之前 ,必須對(duì)該變量 store write
synchronized 隱式的使用lock和unlock剿配,對(duì)于7和9來說搅幅,可以保證線程間變量的可見性
可見性:一個(gè)線程修改了這個(gè)變量的值,其他線程可以立即知道
volatile規(guī)則:
volatile能保證變量在線程之間的可見性
volatile修飾的變量具有的可見性呼胚,用8個(gè)原子操作解釋為: use之前茄唐,必須 read+load,assign之后蝇更,必須 store+write
volatile能禁止指令重排序優(yōu)化
重排序優(yōu)化是機(jī)器級(jí)的優(yōu)化操作沪编,用代碼形象解釋如下:
volatile boolean initialized = false;
//thread A
loadConfig();
initialized = true;
//thread B
while(!initialized){
sleep();
}
doNext();
這代碼意思就是 線程A先 loadConfig(),然后 initialized 為 true,線程B再去進(jìn)行下面工作年扩。如果進(jìn)行重排序優(yōu)化漾抬,則可能先在A線程 先執(zhí)行 initiallized = true,后執(zhí)行l(wèi)oadConfig()常遂,導(dǎo)致B在 A線程 loadConfig() 之前跳出 while循環(huán),顯然不對(duì)挽荠。
volatile如何禁止重排序的呢克胳,使用內(nèi)存屏障
。其實(shí)重排序優(yōu)化是有條件的圈匆,必須前后無依賴漠另,比如 :
A = A+10
A *= 2
類似這里(舉個(gè)栗子,不代表任意代碼) 兩個(gè)語句前后依賴跃赚,就不能重排序笆搓。所謂的內(nèi)存屏障
,就是刻意制造這種依賴纬傲,致使代碼不能重排序满败。(具體操作 lock addl $0x0,(%esp)壁酬,把ESP寄存器的值加0找爱,顯然是一個(gè)空操作,相當(dāng)于做了一次 store + write徘意,只不過啥也沒改汁雷,但是你不能越過這個(gè)操作去執(zhí)行下面的操作
)
從8個(gè)原子操作理解的話净嘀,即為:
T表示線程报咳,所以這里在同一個(gè)線程內(nèi)
A B 表示操作
V W 表示volatile變量
A1:T->V use | assign
A2:T->V load | store
A3:T->V read | write
B1:T->W use | assign
B2:T->W load | store
B3:T->W read | write
A1先于B1 => A3先于B3
========
個(gè)人理解:
V = 1; (執(zhí)行了 A1,A2挖藏,A3)
W = 2暑刃;(執(zhí)行了 B1,B2膜眠,B3)
從代碼上來看岩臣,A3 先于 B3,由于不能重排序 所以 A1 一定先于 B1