volatile關鍵字簡述:
Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比市咽,volatile 變量所需的編碼較少痊银,并且運行時開銷也較少,但是它所能實現(xiàn)的功能也僅是synchronized 的一部分施绎。
volatile變量的讀取和寫入操作導致變量直接在主存中讀寫溯革。從主存中讀取和寫入到主存中比在 cpu 緩存中代價更高 贞绳。訪問volatile變量也阻止了常規(guī)的性能優(yōu)化技術對指令的重排序。所以致稀,你應該只在確實需要加強變量的可見性的時候使用volatile冈闭。
1,java-內存模型
Java內存模型規(guī)定:所有的變量都存儲在主內存中抖单。每條線程中還有自己的工作內存萎攒,線程的工作內存中保存了被該線程所使用到的變量(這些變量是從主內存中拷貝而來)。線程對變量的所有操作(讀取矛绘,賦值)都必須在工作內存中進行耍休。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量值的傳遞均需要通過主內存來完成货矮。
2羊精,java-原子性
原子性:即一個操作或者多個操作 要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行囚玫。
例:銀行轉賬的過程喧锦, A 給 B 轉賬1000元 的情況。 ?
試想一下抓督,如果這2個操作不具備原子性燃少,會造成什么樣的后果。假如從賬戶A減去1000元之后本昏,操作突然中止供汛。這樣就會導致賬戶A雖然減去了1000元,但是賬戶B沒有收到這個轉過來的1000元涌穆。
在Java中,對基本數(shù)據(jù)類型的變量的讀取和賦值操作是原子性操作雀久,即這些操作是不可被中斷的宿稀,要么執(zhí)行,要么不執(zhí)行赖捌。
例:x =10 ? //語句1
? ? ? ?y = x ? ?//語句2
? ? ? ?x++ ? ? //語句3
上面只有語句1,符合原子性祝沸,直接將數(shù)值10賦值給x,也就是說線程執(zhí)行這個語句的會直接將數(shù)值10寫入到工作內存中越庇。
語句2實際上包含2個操作罩锐,它先要去讀取x的值,再將x的值寫入工作內存卤唉,雖然讀取x的值以及 將x的值寫入工作內存 這2個操作都是原子性操作涩惑,但是合起來就不是原子性操作了。
同樣的桑驱,x++ 包括3個操作:讀取x的值竭恬,進行加1操作跛蛋,寫入新的值。
也就是說痊硕,只有簡單的讀取赊级、賦值(而且必須是將數(shù)字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作岔绸。
理解了什么是原子操作理逊,就可以看出,什么時候可以使用``volatile``關鍵字:
- 堆變量的寫入操作盒揉,不依賴變量的當前值或者你能確保只有單個線程更新變量的值挡鞍。
- 對該變量的操作沒有包含在具有其他變量的不變式中(也就是原子操作)。
從上面可以看出预烙,Java內存模型只保證了基本讀取和賦值是原子性操作墨微,如果要實現(xiàn)更大范圍操作的原子性,可以通過synchronized和Lock來實現(xiàn)扁掸。由于synchronized和Lock能夠保證任一時刻只有一個線程執(zhí)行該代碼塊翘县,那么自然就不存在原子性問題了,從而保證了原子性谴分。
3锈麸,java - 可見性
可見性是指當多個線程訪問同一個變量時,一個線程修改了這個變量的值牺蹄,其他線程能夠立即看得到修改的值忘伞。
舉個簡單的例子,看下面這段
6//線程1執(zhí)行的代碼
int i =0;
i =10;
//線程2執(zhí)行的代碼
j = i;
由上面的分析可知沙兰,當線程1執(zhí)行 i =10這句時氓奈,會先把i的初始值加載到工作內存中,然后賦值為10鼎天,那么在線程1的工作內存當中i的值變?yōu)?0了舀奶,卻沒有立即寫入到主存當中。
此時線程2執(zhí)行 j = i斋射,它會先去主存讀取i的值并加載到線程2的工作內存當中育勺,注意此時內存當中i的值還是0,那么就會使得j的值為0罗岖,而不是10.
這就是可見性問題涧至,線程1對變量i修改了之后,線程2 沒有立即看到線程1 修改的值桑包。
Java中的可見性
對于可見性南蓬,Java提供了volatile關鍵字來保證可見性。
當一個共享變量被volatile修飾時,它會保證修改的值會立即被更新到主存蓖康,當有其他線程需要讀取時铐炫,它會去內存中讀取新值。
而普通的共享變量不能保證可見性蒜焊,因為普通共享變量被修改之后倒信,什么時候被寫入主存是不確定的,當其他線程去讀取時泳梆,此時內存中可能還是原來的舊值鳖悠,因此無法保證可見性。
另外优妙,通過synchronized和Lock也能夠保證可見性乘综,synchronized和Lock能保證同一時刻只有一個線程獲取鎖然后執(zhí)行同步代碼,并且在釋放鎖之前會將對變量的修改刷新到主存當中套硼。因此可以保證可見性卡辰。
4,java - 有序性
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行邪意。
例:int ?a ?= 1;
? ? ? ?boolean b=false;
? ? ? ?a=2; ? ?/ /語句1
? ? ? ?b=true; / /語句2
上面代碼定義了一個int型變量九妈,定義了一個boolean類型變量,然后分別對兩個變量進行賦值操作雾鬼。從代碼順序上看萌朱,語句1是在語句2前面的,那么JVM在真正執(zhí)行這段代碼的時候會保證語句1一定會在語句2前面執(zhí)行嗎策菜?不一定晶疼,為什么呢?這里可能會發(fā)生指令重排序(Instruction Reorder)又憨。
指令重排序
一般來說翠霍,處理器為了提高程序運行效率,可能會對輸入代碼進行優(yōu)化竟块,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致壶运,但是它會保證程序最終執(zhí)行結果和代碼順序執(zhí)行的結果是一致的。
比如上面的代碼中浪秘,語句1和語句2誰先執(zhí)行對最終的程序結果并沒有影響,那么就有可能在執(zhí)行過程中埠况,語句2先執(zhí)行而語句1后執(zhí)行耸携。
但是要注意,雖然處理器會對指令進行重排序辕翰,但是它會保證程序最終結果會和代碼順序執(zhí)行結果相同夺衍,那么它靠什么保證的呢?再看下面一個例子:
int ? a =10;//語句1
int ? r =2;//語句2
a = a +3;//語句3
r = a*a;//語句4
這段代碼有4個語句喜命,那么可能的一個執(zhí)行順序是: 2-->1--->3--->4
那么可不可能是這個執(zhí)行順序呢?: 語句2 語句1 語句4 語句3
不可能沟沙,因為處理器在進行重排序時是會考慮指令之間的數(shù)據(jù)依賴性河劝,如果一個指令Instruction 2必須用到Instruction 1的結果,那么處理器會保證Instruction 1會在Instruction 2之前執(zhí)行矛紫。
雖然重排序不會影響單個線程內程序執(zhí)行的結果赎瞎,但是多線程呢?下面看一個例子:
//線程1:
context = loadContext();? //語句1
inited = true;? ? ? ? ? ? //語句2
//線程2:
while(!inited ){
? ? ?sleep()
}
doSomethingwithconfig(context);
上面代碼中颊咬,由于語句1和語句2沒有數(shù)據(jù)依賴性务甥,因此可能會被重排序。假如發(fā)生了重排序喳篇,在線程1執(zhí)行過程中先執(zhí)行語句2敞临,而此是線程2會以為初始化工作已經(jīng)完成,那么就會跳出while循環(huán)麸澜,去執(zhí)行doSomethingwithconfig(context)方法挺尿,而此時context并沒有被初始化,就會導致程序出錯炊邦。
從上面可以看出编矾,指令重排序不會影響單個線程的執(zhí)行,但是會影響到線程并發(fā)執(zhí)行的正確性铣耘。
也就是說洽沟,要想并發(fā)程序正確地執(zhí)行,必須要保證原子性蜗细、可見性以及有序性裆操。只要有一個沒有被保證,就有可能會導致程序運行不正確炉媒。
在Java里面踪区,可以通過volatile關鍵字來保證一定的“有序性”。另外可以通過synchronized和Lock來保證有序性吊骤,很顯然缎岗,synchronized和Lock保證每個時刻是有一個線程執(zhí)行同步代碼,相當于是讓線程順序執(zhí)行同步代碼白粉,自然就保證了有序性传泊。
volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對這個變量的讀操作。
5鸭巴,Synchronized關鍵字
Synchroized實現(xiàn)可見性
線程執(zhí)行互斥代碼的過程:
1眷细,獲得互斥鎖
2,清空工作內存
3鹃祖,從主內存拷貝變量的最新副本到工作內存
4溪椎,執(zhí)行鎖中的代碼
5,將更改后的共享變量的值刷新到住內存
6,釋放互斥鎖
volatile 和 synchronized的區(qū)別
1校读,volatile的本質是告訴jvm當前的變量在工作內存(寄存器)中的值是不確定的沼侣,需要從主內存中讀取歉秫;synchronized是鎖定當前的變量蛾洛,只有當前的線程可以訪問,其他的線程被阻塞端考。
2雅潭,volatile僅可以使用在變量級別;synchronized則可以使用在變量却特、方法扶供、類級別。
3裂明,volatile僅可以保證變量的可見性椿浓,不能保證原子性;而synchronized則可以保證變量的可見性和原子性闽晦。
4扳碍,volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞仙蛉。
5笋敞,volatile可以阻止指令重排序