Java 并發(fā)編程問(wèn)題
在并發(fā)編程中试读,我們通常會(huì)遇到以下三個(gè)問(wèn)題:原子性問(wèn)題锨咙,可見(jiàn)性問(wèn)題嘶炭,有序性問(wèn)題。這些問(wèn)題發(fā)生的原因是 Java 的內(nèi)存模式?jīng)Q定的歹鱼。我們先看看 Java 的內(nèi)存模型泣栈,再來(lái)解釋一下上面的三個(gè)問(wèn)題。
Java 內(nèi)存模型
Java內(nèi)存模型規(guī)定所有的變量都是存在主存當(dāng)中(堆內(nèi)存)弥姻,每個(gè)線程都有自己的工作內(nèi)存(方法棧)南片。線程對(duì)變量的所有操作都必須在工作內(nèi)存中進(jìn)行,而不能直接對(duì)主存進(jìn)行操作庭敦。并且每個(gè)線程不能訪問(wèn)其他線程的工作內(nèi)存疼进。并且在工作內(nèi)存中進(jìn)行的操作并不是實(shí)時(shí)寫(xiě)入到主內(nèi)存中的。
原子性
原子性:即一個(gè)操作或者多個(gè)操作
要么全部執(zhí)行并且執(zhí)行的過(guò)程不會(huì)被任何因素打斷秧廉,要么就都不執(zhí)行伞广。經(jīng)典轉(zhuǎn)賬問(wèn)題不是原子性操作(因?yàn)椴⒉皇且徊讲僮?
x = 10; // 是原子性操作拣帽,不需要讀取 x 的值,只進(jìn)行賦值到內(nèi)存嚼锄,一步完成减拭,所以說(shuō)原子性操作
y = x; // 不是原子性操作,需要第一步讀取 x 的值区丑,第二步為 y 賦值到內(nèi)存中拧粪,所以不是原子性操作
在Java中,對(duì)基本數(shù)據(jù)類(lèi)型的變量的讀取和賦值操作是原子性操作刊苍,即這些操作是不可被中斷的既们,要么執(zhí)行濒析,要么不執(zhí)行正什。只有簡(jiǎn)單的讀取、賦值(而且必須是將數(shù)字賦值給某個(gè)變量号杏,變量之間的相互賦值不是原子操作)才是原子操作婴氮。
Java內(nèi)存模型只保證了基本讀取和賦值是原子性操作,如果要實(shí)現(xiàn)更大范圍操作的原子性盾致,可以通過(guò) synchronized 和 Lock 來(lái)實(shí)現(xiàn)主经。由于 synchronized 和 Lock 能夠保證任一時(shí)刻只有一個(gè)線程執(zhí)行該代碼塊,那么自然就不存在原子性問(wèn)題了庭惜,從而保證了原子性罩驻。
可見(jiàn)性
可見(jiàn)性是指當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值护赊,會(huì)馬上寫(xiě)入到主內(nèi)存惠遏,其他線程能夠立即看得到修改的值。
解釋?zhuān)嚎梢?jiàn)性是指骏啰,一個(gè)線程改變了這個(gè)變量的值节吮,這個(gè)值會(huì)馬上寫(xiě)入到主內(nèi)存,其他線程訪問(wèn)時(shí)會(huì)得到已經(jīng)改變了的這個(gè)值
對(duì)于可見(jiàn)性判耕,Java 提供了 volatile 關(guān)鍵字來(lái)保證可見(jiàn)性透绩。
當(dāng)一個(gè)共享變量被 volatile 修飾時(shí),它會(huì)保證修改的值會(huì)立即被更新到主存壁熄,當(dāng)有其他線程需要讀取時(shí)帚豪,會(huì)在內(nèi)存中讀取到最新值。
而普通的共享變量不能保證可見(jiàn)性草丧,因?yàn)槠胀ü蚕碜兞勘恍薷闹罄瓿迹裁磿r(shí)候被寫(xiě)入主存是不確定的,當(dāng)其他線程去讀取時(shí)方仿,此時(shí)內(nèi)存中可能還是原來(lái)的舊值固棚,因此無(wú)法保證可見(jiàn)性统翩。
另外,通過(guò) synchronized 和 Lock 也能夠保證可見(jiàn)性此洲,synchronized 和 Lock 能保證同一時(shí)刻只有一個(gè)線程獲取鎖然后執(zhí)行同步代碼厂汗,并且在釋放鎖之前會(huì)將對(duì)變量的修改刷新到主存當(dāng)中。因此可以保證可見(jiàn)性呜师。
當(dāng)線程 1 修改了一個(gè)變量的值娶桦,會(huì)馬上寫(xiě)入內(nèi)存,當(dāng)線程 2 讀取時(shí)會(huì)讀取到最新的
當(dāng)線程 1 讀取了一個(gè)變量的值汁汗,接著線程 1 阻塞衷畦,線程 2 接著讀取并修改該變量的值,線程 1 再操作時(shí)不會(huì)收到提示知牌,會(huì)操作舊的值 由于線程 1 中的操作可能不是原子操作祈争,所以可能出現(xiàn)上述問(wèn)題
有序性
有序性:即程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。當(dāng)代碼執(zhí)行順序不同時(shí)角寸,多線程就會(huì)出現(xiàn)問(wèn)題菩混。
//線程1:
context = loadContext(); //語(yǔ)句1
inited = true; //語(yǔ)句2
//線程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
如果線程1修改 inited 為 true 但是沒(méi)有執(zhí)行語(yǔ)句1,線程2中使用 context 就會(huì)出問(wèn)題扁藕。
在Java內(nèi)存模型中沮峡,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行亿柑,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性邢疙。
以上就是由于 Java 的內(nèi)存模型引起的并發(fā)編程時(shí)遇到的三個(gè)問(wèn)題,這三個(gè)問(wèn)題的解決 Java 也提供了量中場(chǎng)景的機(jī)制望薄,使用 volatile 關(guān)鍵字或者使用 synchronized 同步鎖
一疟游、volatile
一旦一個(gè)共享變量(類(lèi)的成員變量、類(lèi)的靜態(tài)成員變量)被 volatile 修飾之后式矫,那么就具備了兩層語(yǔ)義:
保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性乡摹,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的采转。
禁止進(jìn)行指令重排序聪廉。
注意:volatile 不能保證原子性
volatile 可見(jiàn)性
這段代碼是很典型的一段代碼,很多人在中斷線程時(shí)可能都會(huì)采用這種標(biāo)記辦法故慈。但是事實(shí)上板熊,這段代碼會(huì)完全運(yùn)行正確么?即一定會(huì)將線程中斷么察绷?不一定干签,也許在大多數(shù)時(shí)候,這個(gè)代碼能夠把線程中斷拆撼,但是也有可能會(huì)導(dǎo)致無(wú)法中斷線程(雖然這個(gè)可能性很小容劳,但是只要一旦發(fā)生這種情況就會(huì)造成死循環(huán)了)喘沿。
//線程1
boolean stop = false;
while(!stop){
doSomething();
}
//線程2
stop = true;
下面解釋一下這段代碼為何有可能導(dǎo)致無(wú)法中斷線程。在前面已經(jīng)解釋過(guò)竭贩,每個(gè)線程在運(yùn)行過(guò)程中都有自己的工作內(nèi)存蚜印,那么線程1在運(yùn)行的時(shí)候,會(huì)將stop變量的值拷貝一份放在自己的工作內(nèi)存當(dāng)中留量。
那么當(dāng)線程2更改了stop變量的值之后窄赋,但是還沒(méi)來(lái)得及寫(xiě)入主存當(dāng)中,線程2轉(zhuǎn)去做其他事情了楼熄,那么線程1由于不知道線程2對(duì)stop變量的更改忆绰,因此還會(huì)一直循環(huán)下去。
但是用volatile修飾之后就變得不一樣了:
第一:使用volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫(xiě)入主存可岂;
第二:使用volatile關(guān)鍵字的話错敢,當(dāng)線程2進(jìn)行修改時(shí),會(huì)導(dǎo)致線程1的工作內(nèi)存中緩存變量stop的緩存行無(wú)效(反映到硬件層的話青柄,就是CPU的L1或者L2緩存中對(duì)應(yīng)的緩存行無(wú)效)伐债;
第三:由于線程1的工作內(nèi)存中緩存變量stop的緩存行無(wú)效预侯,所以線程1再次讀取變量stop的值時(shí)會(huì)去主存讀取致开。那么線程1讀取到的就是最新的正確的值。
自增并不是原子性操作萎馅,所以多個(gè)線程同時(shí)操作一個(gè)內(nèi)存中的一個(gè)數(shù)字自增操作双戳,使用 volatile 修飾該對(duì)象時(shí)會(huì)出現(xiàn)問(wèn)題
volatile 有序性
當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫(xiě)操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行糜芳,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn)飒货;在其后面的操作肯定還沒(méi)有進(jìn)行;
Java 在進(jìn)行指令優(yōu)化時(shí)峭竣,不能將在對(duì)volatile變量訪問(wèn)的語(yǔ)句放在其后面執(zhí)行塘辅,也不能把volatile變量后面的語(yǔ)句放到其前面執(zhí)行。
volatile 使用場(chǎng)景
- 狀態(tài)標(biāo)記量
- double check 單例模式時(shí)使用皆撩,使用 volatile 目的是為了保證可見(jiàn)性扣墩,當(dāng)一個(gè)線程中對(duì)對(duì)象有修改之后其他線程可見(jiàn)。
二扛吞、使用 synchronized 同步鎖
在需要同步的時(shí)候呻惕,第一選擇應(yīng)該是synchronized關(guān)鍵字,這是最安全的方式滥比,嘗試其他任何方式都是有風(fēng)險(xiǎn)的亚脆。使用同步鎖可以實(shí)現(xiàn)解決原子性、可見(jiàn)性盲泛、有序性等問(wèn)題
http://www.cnblogs.com/dolphin0520/p/3920373.html
http://blog.csdn.net/ns_code/article/details/17290021