Java并發(fā)機(jī)制底層實(shí)現(xiàn)原理

在文章開(kāi)始之前腥光,先分享下奶奶家養(yǎng)的可愛(ài)小貓咪 233333

在現(xiàn)如今的軟件開(kāi)發(fā)領(lǐng)域关顷,并發(fā)編程是老生常談的東西。但是要理解并掌握好并發(fā)編程卻并不是那么容易的事情武福。對(duì)于我來(lái)說(shuō)议双,在學(xué)習(xí)過(guò)程中能夠應(yīng)用到并發(fā)編程的場(chǎng)景不是很多,所以更多的東西一直都是停留在理論層面捉片,或者是僅僅停留在Java語(yǔ)言的層面平痰。為了在以后的工作當(dāng)中更順利地進(jìn)行并發(fā)編程汞舱,我一直都在學(xué)習(xí)這方面的知識(shí)。之前閱讀過(guò)《Java并發(fā)編程實(shí)戰(zhàn)》一書(shū)宗雇,也看過(guò)JDK包中一些并發(fā)容器類(lèi)以及同步容器類(lèi)的源碼昂芜,同時(shí)也堅(jiān)持閱讀相關(guān)的博客,所以對(duì)并發(fā)編程有一點(diǎn)點(diǎn)淺顯的了解赔蒲,為了加深自己對(duì)并發(fā)相關(guān)知識(shí)的理解泌神,我打算針對(duì)Java并發(fā)編程這一塊寫(xiě)一些讀書(shū)筆記并分享一些自己的學(xué)習(xí)心得。

本文暫時(shí)只對(duì)Java虛擬機(jī)自帶的鎖機(jī)制以及volatile等知識(shí)進(jìn)行了總結(jié)舞虱,并沒(méi)有涉及JUC包中類(lèi)的介紹欢际,后期會(huì)加上這一部分。

volatile實(shí)現(xiàn)原理

volatile在并發(fā)編程中扮演著重要的角色矾兜,volatile就像是輕量級(jí)的synchronized损趋,它在多處理器中保證了共享變量的可見(jiàn)性∫嗡拢可見(jiàn)性的意思是當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí)浑槽,其他線程能夠及時(shí)讀取到這個(gè)修改的值。由于volatile并不會(huì)引起線程上下文的切換和調(diào)度返帕,所以它比synchronized的使用和執(zhí)行成本更低括荡。volatile在并發(fā)編程中還起著另一個(gè)作用---那就是禁止指令重排。我們知道溉旋,編譯器在對(duì)Java代碼進(jìn)行優(yōu)化的時(shí)候可能會(huì)發(fā)生指令重排畸冲,指令重排對(duì)于CPU來(lái)說(shuō)就是指令亂序執(zhí)行,這是多條指令在流水線上執(zhí)行观腊,很好地利用了多處理器邑闲。雖然這樣在單線程語(yǔ)義中不會(huì)發(fā)生什么錯(cuò)誤,但是可能在多線程中會(huì)出現(xiàn)一些并發(fā)性問(wèn)題梧油,關(guān)于這一點(diǎn)我在后面會(huì)提到苫耸。首先來(lái)說(shuō)說(shuō)volatile如何保證可見(jiàn)性。

volatile保證可見(jiàn)性

Java語(yǔ)言規(guī)范第3版中對(duì)volatile的定義是這樣的:Java編程語(yǔ)言允許線程訪問(wèn)共享變量儡陨,為了確保共享變量能被準(zhǔn)確和一致地更新褪子,線程應(yīng)該確保通過(guò)排他鎖單獨(dú)獲得這個(gè)變量。Java語(yǔ)言提供了volatile骗村,在某些情況下比鎖要更方便嫌褪。如果一個(gè)字段聲明成volatile,Java線程內(nèi)存模型確保所有線程看到這個(gè)變量的值是一致的胚股。
那么volatile是如何保證可見(jiàn)性的呢笼痛?

在對(duì)volatile修飾的變量進(jìn)行寫(xiě)操作的時(shí),轉(zhuǎn)變成的匯編代碼會(huì)多出一條Lock前綴的指令,Lock前綴的指令在多核處理器下引發(fā)了兩件事情:

  • 將當(dāng)前處理器緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存缨伊;
  • 這個(gè)寫(xiě)回內(nèi)存的操作會(huì)使其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效摘刑。

為了提高處理速度,處理器不直接與內(nèi)存進(jìn)行通信刻坊,而是先將系統(tǒng)內(nèi)存的數(shù)據(jù)讀到內(nèi)部緩存后再進(jìn)行操作枷恕,但操作完不知道何時(shí)會(huì)寫(xiě)到內(nèi)存。如果對(duì)被volatile聲明的變量進(jìn)行寫(xiě)操作谭胚,JVM虛擬機(jī)就會(huì)向處理器發(fā)送一條Lock前綴的指令活尊,將這個(gè)變量所在緩存行的數(shù)據(jù)寫(xiě)回到內(nèi)存當(dāng)中。同時(shí)為了保證各個(gè)處理器的緩存是一致的漏益,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議,每個(gè)處理器通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查自己緩存的值是不是過(guò)期了深胳,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改绰疤,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作的時(shí)候舞终,會(huì)重新從系統(tǒng)內(nèi)存中把數(shù)據(jù)讀到處理器緩存當(dāng)中轻庆。

volatile的兩條實(shí)現(xiàn)原則底層實(shí)現(xiàn)

  • Lock前綴指令會(huì)引起處理器緩存回寫(xiě)到內(nèi)存:Lock前綴指令導(dǎo)致在執(zhí)行指令期間,聲言處理器的LOCK#信號(hào)敛劝。在多處理器環(huán)境中余爆,LOCK#信號(hào)確保在聲言該信號(hào)期間,處理器可以獨(dú)占任何共享內(nèi)存(在以前的處理器當(dāng)中夸盟,這條指令會(huì)鎖住總線導(dǎo)致其他CPU無(wú)法訪問(wèn)總線從而限制其他處理器無(wú)法訪問(wèn)系統(tǒng)內(nèi)存)蛾方。但是在現(xiàn)在的處理器中,一般不會(huì)鎖總線而是鎖緩存上陕,因?yàn)殒i總線的開(kāi)銷(xiāo)比較大桩砰。對(duì)于某些處理器來(lái)說(shuō),它們?cè)阪i操作時(shí)释簿,總是在總線上聲言LOCK#信號(hào)亚隅。但是對(duì)于另外一些處理器來(lái)說(shuō),它們不會(huì)聲言LOCK#信號(hào)庶溶。相反煮纵,它會(huì)鎖定這塊內(nèi)存區(qū)域的緩存并回寫(xiě)到內(nèi)存,并使用緩存一致性機(jī)制來(lái)確保修改的原子性偏螺,此操作被稱為“緩存鎖定”行疏,緩存一致性機(jī)制會(huì)阻止同時(shí)修改由兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù)。
  • 一個(gè)處理器的緩存回寫(xiě)到內(nèi)存會(huì)導(dǎo)致其它處理器的緩存無(wú)效:一些處理使用MESI控制協(xié)議去維護(hù)內(nèi)部緩存和其他處理器緩存的一致性套像。在多核處理器系統(tǒng)中進(jìn)行操作的時(shí)候隘擎,一些處理器能夠嗅探其他處理器訪問(wèn)系統(tǒng)內(nèi)存和它們的內(nèi)部緩存。處理器使用嗅探技術(shù)保證它的內(nèi)部緩存凉夯、系統(tǒng)內(nèi)存和其他處理器的緩存的數(shù)據(jù)在總線上保持一致货葬。例如采幌,在某些處理器中如果通過(guò)嗅探一個(gè)處理器來(lái)檢測(cè)其它處理器打算寫(xiě)內(nèi)存地址,而這個(gè)地址當(dāng)前處于共享狀態(tài)震桶,那么正在嗅探的處理器將使它的緩存無(wú)效休傍,在下次訪問(wèn)相同內(nèi)存地址時(shí),強(qiáng)制執(zhí)行緩存行填充蹲姐。
volatile禁止指令重排

volatile為寫(xiě)-讀建立happens-before關(guān)系
在多處理器對(duì)指令進(jìn)行流水線處理時(shí)磨取,不可避免地會(huì)發(fā)生亂序執(zhí)行,即指令重排的情況發(fā)生柴墩。在某些時(shí)候忙厌,這種優(yōu)化可能會(huì)導(dǎo)致并發(fā)性問(wèn)題。但是volatile能夠禁止指令重排江咳,其底層是通過(guò)在讀和寫(xiě)指令前后插入內(nèi)存屏障實(shí)現(xiàn)的逢净。下面以DCL單例為例進(jìn)行說(shuō)明:

DCL雙鎖檢測(cè)單例模式

上面這個(gè)DCL懶漢式看起來(lái)很美好地實(shí)現(xiàn)單例模式----先檢測(cè)singleton對(duì)象是否為空,如果為空就鎖定Singleton類(lèi)歼指,接著再次檢測(cè)singleton對(duì)象是否為空爹土,若為空就進(jìn)行實(shí)例化操作最后返回實(shí)例化的對(duì)象水由。但是它會(huì)存在不安全發(fā)布的情況---即對(duì)象還未完成初始化就發(fā)布出去了晶乔,將會(huì)引發(fā)一系列的問(wèn)題。

下面我們?cè)敿?xì)說(shuō)說(shuō)為什么會(huì)發(fā)生不安全發(fā)布的問(wèn)題
這里是singleton對(duì)象初始化的過(guò)程:

  1. 分配對(duì)象的內(nèi)存空間胁后;
  1. 初始化對(duì)象挟阻;
  2. 設(shè)置singleton指向剛剛分配的內(nèi)存空間琼娘。

上面是正常初始化一個(gè)對(duì)象的流程「礁耄可是由于指令重排的存在轨奄,步驟2和步驟3可能會(huì)亂序執(zhí)行。此時(shí)假如有線程A和線程B同時(shí)調(diào)用上面代碼中的getSingleton()函數(shù)拒炎,假設(shè)A先B一步已經(jīng)開(kāi)始進(jìn)行對(duì)象初始化過(guò)程了挪拟。由于指令重排的存在,A可能先進(jìn)行步驟3的操作----設(shè)置singleton指向分配的內(nèi)存空間击你。此時(shí)B才開(kāi)始調(diào)用getSingleton()方法玉组,想要獲得一個(gè)singleton對(duì)象。然后它發(fā)現(xiàn)singleton指向的內(nèi)存區(qū)域不為空丁侄,就直接返回了singleton惯雳。可是此時(shí)線程A由于亂序執(zhí)行的原因鸿摇,可能尚未進(jìn)行對(duì)象的初始化或者是對(duì)象初始化還沒(méi)有完成石景,這就導(dǎo)致了一個(gè)沒(méi)有完全初始化的對(duì)象被發(fā)布了,這可能導(dǎo)致非常嚴(yán)重的問(wèn)題。所以DCL雙鎖單例模式并不完美潮孽。
但是假如把對(duì)象用volatile關(guān)鍵字修飾揪荣,那就可以避免在對(duì)象進(jìn)行初始化的過(guò)程中發(fā)生指令重排的情況,從而避免對(duì)象的不安全發(fā)布現(xiàn)象的發(fā)生往史。

在DCL雙鎖單利模式中用volatile關(guān)鍵字修飾對(duì)象

既然提到了單例仗颈,那么下面就介紹另一種線程安全的單例模式實(shí)現(xiàn)

基于類(lèi)初始化的安全單例模式

JVM在類(lèi)初始化階段(即在Class被加載后,且被線程使用之前)椎例,會(huì)執(zhí)行類(lèi)的初始化操作挨决。在執(zhí)行類(lèi)的初始化期間,JVM會(huì)去獲取一個(gè)鎖订歪。這個(gè)鎖可以同步多個(gè)線程對(duì)同一個(gè)類(lèi)的初始化脖祈。基于這個(gè)特性刷晋,可以實(shí)現(xiàn)另一種線程安全的延遲初始化方案:

基于類(lèi)初始化的安全單例模式

Java語(yǔ)言規(guī)范規(guī)定盖高,對(duì)于每一個(gè)類(lèi)或接口C,都有一個(gè)唯一的初始化鎖LC與之對(duì)應(yīng)掏秩。從C到LC的映射,由JVM的具體實(shí)現(xiàn)去自由實(shí)現(xiàn)荆姆。JVM在類(lèi)初始化期間會(huì)獲取這個(gè)初始化鎖蒙幻,并且每個(gè)線程至少獲取一次鎖來(lái)確保這個(gè)類(lèi)已經(jīng)被初始化了。

synchronized實(shí)現(xiàn)原理

synchronized在大部分時(shí)候是實(shí)現(xiàn)同步的基礎(chǔ):Java中每一個(gè)對(duì)象都可以作為鎖胆筒,具體表現(xiàn)為以下3種形式:

  • 對(duì)于普通同步方法邮破,鎖是當(dāng)前實(shí)例對(duì)象;
  • 對(duì)于靜態(tài)同步方法仆救,鎖是當(dāng)前類(lèi)的Class對(duì)象抒和;
  • 對(duì)于同步方法塊,鎖是synchronized括號(hào)里配置的對(duì)象彤蔽。

關(guān)于synchronized底層實(shí)現(xiàn)原理摧莽,我在這篇博客里面介紹了。
Java虛擬機(jī)規(guī)范
JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法同步以及代碼塊同步顿痪,但兩者的實(shí)現(xiàn)細(xì)節(jié)不太一樣镊辕,代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,方法同步是在方法表中添加一個(gè)ACC_SYNC的標(biāo)志位蚁袭,但是方法的同步同樣可以使用這兩個(gè)指令來(lái)實(shí)現(xiàn)征懈。

鎖的升級(jí)與對(duì)比

鎖一共有四種狀態(tài),級(jí)別由低到高一次是:無(wú)鎖狀態(tài)揩悄、偏向鎖狀態(tài)卖哎、輕量級(jí)鎖狀態(tài)、重量級(jí)鎖狀態(tài)。這幾個(gè)狀態(tài)會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)亏娜,鎖可以升級(jí)但不可以降級(jí)(這里和后面提到的ReentrantReadWriteLock中的寫(xiě)鎖降級(jí)為讀鎖不是一回事)焕窝,即輕量級(jí)鎖升級(jí)為重量級(jí)鎖之后不能降級(jí)成輕量級(jí)鎖。這種鎖升級(jí)后不能降級(jí)的策略的目的是為了提高獲取鎖和釋放鎖的效率照藻。

偏向鎖

大多數(shù)情況下袜啃,鎖不僅不存在多線程競(jìng)爭(zhēng),而且總是由同一線程多次獲得幸缕,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖群发。當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄當(dāng)中存儲(chǔ)鎖偏向的線程ID发乔,以后該線程在進(jìn)入和退出同步塊時(shí)不需要進(jìn)行CAS操作來(lái)加鎖和解鎖熟妓,只需要簡(jiǎn)單地測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖。如果測(cè)試成功則代表當(dāng)前線程已經(jīng)獲得了鎖栏尚,如果測(cè)試失敗則需要再測(cè)試一下Mark Word中偏向鎖的標(biāo)志是否設(shè)置成為了1(表示當(dāng)前是偏向鎖):如果沒(méi)有設(shè)置起愈,則使用CAS競(jìng)爭(zhēng)鎖,如果設(shè)置了译仗,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程抬虽。

偏向鎖的撤銷(xiāo)

偏向鎖使用了一種等到出現(xiàn)競(jìng)爭(zhēng)才釋放鎖的機(jī)制,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖的時(shí)候纵菌,持有偏向鎖的線程才會(huì)釋放鎖阐污。偏向鎖的撤銷(xiāo),需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有正在執(zhí)行的字節(jié)碼)咱圆。它會(huì)首先暫停擁有偏向鎖的線程笛辟,然后檢查持有偏向鎖的線程是否還活著,如果線程不處于活動(dòng)狀態(tài)序苏,則將對(duì)象頭設(shè)置為無(wú)所狀態(tài)手幢;如果線程仍然活著,持有偏向鎖的棧會(huì)被執(zhí)行忱详,遍歷對(duì)象的鎖記錄围来,棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向于其他線程,要么恢復(fù)到無(wú)鎖或者標(biāo)記對(duì)象不適合所謂偏向鎖匈睁,最后喚醒暫停的線程管钳。

關(guān)閉偏向鎖

偏向鎖在jdk1.6和jdk1.7里面是默認(rèn)啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒之后才激活软舌,如有必要可使用參數(shù)來(lái)關(guān)閉延遲:-XX:BiasedLockingStartupDelay=0才漆。如果我們確定應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)參數(shù)來(lái)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false佛点,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)醇滥。

輕量級(jí)鎖

輕量級(jí)鎖加鎖

線程在執(zhí)行同步塊之前黎比,JVM會(huì)先在當(dāng)前線程的棧幀中創(chuàng)建存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中鸳玩,即Displaced Mark Word阅虫。然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。若成功不跟,當(dāng)前線程獲得鎖颓帝;若失敗,表示其他線程競(jìng)爭(zhēng)鎖窝革,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖购城。

輕量級(jí)鎖解鎖

輕量級(jí)鎖解鎖時(shí),會(huì)使用原子的CAS操作將Displaced Mark Word替換到對(duì)象頭虐译。如果成功瘪板,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗漆诽,表示當(dāng)前鎖存在競(jìng)爭(zhēng)侮攀,鎖就會(huì)膨脹成重量級(jí)鎖。

優(yōu)點(diǎn) 缺點(diǎn) 適用場(chǎng)景
偏向鎖 加鎖和解鎖不需要額外的消耗厢拭,和執(zhí)行非同步方法相比僅存在納秒級(jí)的差距 如果線程間存在鎖競(jìng)爭(zhēng)兰英,會(huì)帶來(lái)額外的消耗 適用于只有一個(gè)線程訪問(wèn)同步塊場(chǎng)景
輕量級(jí)鎖 競(jìng)爭(zhēng)的線程不會(huì)阻塞,提高了程序的響應(yīng)速度 如果始終得不到鎖競(jìng)爭(zhēng)的線程供鸠,使用自旋會(huì)消耗CPU 追求響應(yīng)速度畦贸,同步塊執(zhí)行速度非常快
重量級(jí)鎖 線程競(jìng)爭(zhēng)不會(huì)自旋回季,不會(huì)消耗CPU資源 線程阻塞家制,響應(yīng)速度緩慢 追求吞吐量正林,同步塊執(zhí)行時(shí)間比較長(zhǎng)

原子操作實(shí)現(xiàn)原理

處理器如何實(shí)現(xiàn)原子操作
  • 使用總線鎖保證原子性泡一。第一個(gè)機(jī)制是通過(guò)總線鎖保證原子性,所謂總線鎖就是使用處理器提供的一個(gè)LOCK#信號(hào)觅廓,當(dāng)一個(gè)處理器在總線上輸出此信號(hào)時(shí)鼻忠,其他處理器的請(qǐng)求將被阻塞住,那么該處理器可以獨(dú)占共享內(nèi)存杈绸;
  • 使用緩存鎖保證原子性帖蔓。因?yàn)樵谕粫r(shí)刻,我們只需保證對(duì)某個(gè)內(nèi)存地址的操作是原子性即可瞳脓,但總線鎖把CPU和內(nèi)存之間的通信鎖住了塑娇,這使得鎖定期間,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù)劫侧,所以總線鎖定的開(kāi)銷(xiāo)比較大埋酬,目前處理器在某些場(chǎng)合下使用緩存鎖定代替總線鎖定來(lái)進(jìn)行優(yōu)化哨啃。

但是在這兩種情況下處理器不會(huì)使用緩存鎖定:

  • 當(dāng)操作的數(shù)據(jù)不能被還存在處理器內(nèi)部,或操作的數(shù)據(jù)跨多個(gè)緩存行時(shí)写妥,處理器會(huì)調(diào)用總線鎖定拳球;
  • 有些處理器不支持緩存鎖定。
Java中如何實(shí)現(xiàn)原子操作

在Java中可以通過(guò)鎖和循環(huán)CAS來(lái)實(shí)現(xiàn)原子操作珍特。從jdk1.5開(kāi)始祝峻,JDK的并發(fā)包里面提供了一些類(lèi)來(lái)支持原子操作,如AtomicBoolean(用原子的方式更新的boolean值)扎筒、AtomicInteger(用原子方式更新的int值)和AtomicLong等莱找。這些原子包裝類(lèi)提供了有用的工具方法,例如以原子的方式將當(dāng)前值自增1或自減1砸琅。
CAS實(shí)現(xiàn)原子操作的三大問(wèn)題

  • ABA問(wèn)題宋距;
  • 循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大;
  • 只能保證一個(gè)共享變量的原子操作症脂。從jdk1.5開(kāi)始谚赎,JDK提供了AtomicReference類(lèi)來(lái)保證引用對(duì)象之間的原子性,就可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作诱篷。
使用鎖機(jī)制實(shí)現(xiàn)原子操作

鎖機(jī)制保證了只有獲得鎖的線程才能操作鎖定的內(nèi)存區(qū)域壶唤。JVM內(nèi)部實(shí)現(xiàn)了很多種鎖機(jī)制,有偏向鎖棕所、輕量級(jí)鎖和互斥鎖闸盔。有意思的是,除了偏向鎖琳省,JVM實(shí)現(xiàn)鎖的方式都使用了循環(huán)CAS迎吵,即當(dāng)一個(gè)線程想進(jìn)入同步塊的時(shí)候使用循環(huán)CAS的方式來(lái)獲取鎖,當(dāng)它退出同步塊的時(shí)候使用循環(huán)CAS釋放鎖针贬。

final的內(nèi)存語(yǔ)義

與鎖和volatile相比击费,對(duì)final域的讀寫(xiě)更像是普通的變量訪問(wèn)。但其實(shí)final在某些時(shí)候也可以用來(lái)防止對(duì)象的不安全發(fā)布桦他,下面來(lái)說(shuō)說(shuō)final的內(nèi)存語(yǔ)義蔫巩。

final的重排序規(guī)則

對(duì)于final域,編譯器和處理器要遵守兩個(gè)重排序規(guī)則:

  • 在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final域的寫(xiě)入快压,與隨后把這個(gè)構(gòu)造函數(shù)的引用賦值給一個(gè)引用的變量圆仔,這兩個(gè)操作之間不能重排序;
  • 初次都一個(gè)包含final域的對(duì)象的引用蔫劣,與隨后初次讀這個(gè)final域坪郭,這兩個(gè)操作之間不能重排序。
寫(xiě)final域的重排序規(guī)則
  • JMM禁止編譯器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外脉幢;
  • 編譯器會(huì)在final域的寫(xiě)之后歪沃,構(gòu)造函數(shù)return之前信姓,插入一個(gè)StoreStore屏障。這個(gè)屏障禁止處理器把final域的寫(xiě)重排序到構(gòu)造函數(shù)之外绸罗。
讀final域的重排序規(guī)則

讀final域的重排序規(guī)則是意推,在一個(gè)線程中,初次讀對(duì)象引用與初次讀該對(duì)象包含的final域珊蟀,JMM禁止處理器重排序這兩個(gè)操作(這里要注意菊值,此規(guī)則僅僅針對(duì)處理器)。編譯器會(huì)在讀final域操作的前面插入一個(gè)LoadLoad屏障育灸。
初次讀對(duì)象引用與初次讀該對(duì)象包含的final域腻窒,這兩個(gè)操作之間存在間接依賴關(guān)系。由于編譯器遵守間接依賴關(guān)系磅崭,因此編譯器不會(huì)重排序這兩個(gè)操作儿子。大多數(shù)處理器也會(huì)遵守間接依賴,也不會(huì)遵守重排序這兩個(gè)操作砸喻。讀final域的重排序規(guī)則可以確保:在讀一個(gè)對(duì)象的final域之前柔逼,一定會(huì)先讀包含這個(gè)final域的對(duì)象的引用,如果該引用不為空割岛,那么引用對(duì)象的final域一定已經(jīng)被A線程初始化過(guò)了愉适。

如果final域?yàn)橐妙?lèi)型

對(duì)于引用類(lèi)型,寫(xiě)final域的重排序規(guī)則對(duì)編譯器和處理器做了如下約束:在構(gòu)造函數(shù)內(nèi)對(duì)一個(gè)final引用的對(duì)象的成員域的寫(xiě)入癣漆,與隨后在構(gòu)造函數(shù)外吧這個(gè)被構(gòu)造對(duì)象的引用賦值給一個(gè)引用變相维咸,這兩個(gè)操作之間不能重排序。

寫(xiě)final域的規(guī)則可以確保在引用變量為任意線程可見(jiàn)之前惠爽,該引用變量指向的對(duì)象的final域已經(jīng)在構(gòu)造函數(shù)中被正確初始化過(guò)了癌蓖。其實(shí)要得到這個(gè)效果,還需要一個(gè)保證:那就是在構(gòu)造函數(shù)內(nèi)部婚肆,不能讓這個(gè)被構(gòu)造對(duì)象的引用為其他線程所見(jiàn)租副,也就是對(duì)象引用不能再構(gòu)造函數(shù)中"逸出",即不能發(fā)生this逸出的情況旬痹。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末附井,一起剝皮案震驚了整個(gè)濱河市讨越,隨后出現(xiàn)的幾起案子两残,更是在濱河造成了極大的恐慌,老刑警劉巖把跨,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件人弓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡着逐,警方通過(guò)查閱死者的電腦和手機(jī)崔赌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén)意蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人健芭,你說(shuō)我怎么就攤上這事县钥。” “怎么了慈迈?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵若贮,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我痒留,道長(zhǎng)谴麦,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任伸头,我火速辦了婚禮匾效,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘恤磷。我一直安慰自己面哼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布扫步。 她就那樣靜靜地躺著精绎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锌妻。 梳的紋絲不亂的頭發(fā)上代乃,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音仿粹,去河邊找鬼搁吓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吭历,可吹牛的內(nèi)容都是我干的堕仔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼晌区,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼摩骨!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起朗若,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恼五,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后哭懈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體灾馒,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年遣总,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了睬罗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轨功。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖容达,靈堂內(nèi)的尸體忽然破棺而出古涧,到底是詐尸還是另有隱情,我是刑警寧澤花盐,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布蒿褂,位于F島的核電站,受9級(jí)特大地震影響卒暂,放射性物質(zhì)發(fā)生泄漏啄栓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一也祠、第九天 我趴在偏房一處隱蔽的房頂上張望昙楚。 院中可真熱鬧,春花似錦诈嘿、人聲如沸堪旧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)淳梦。三九已至,卻和暖如春昔字,著一層夾襖步出監(jiān)牢的瞬間爆袍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工作郭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留陨囊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓夹攒,卻偏偏與公主長(zhǎng)得像蜘醋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咏尝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11压语、字符串不變性 12、equals()方法编检、hashCode()方法的區(qū)別 13胎食、...
    Miley_MOJIE閱讀 3,693評(píng)論 0 11
  • 并發(fā)系列的文章都是根據(jù)閱讀《Java 并發(fā)編程的藝術(shù)》這本書(shū)總結(jié)而來(lái),想更深入學(xué)習(xí)的同學(xué)可以自行購(gòu)買(mǎi)此書(shū)進(jìn)行學(xué)習(xí)蒙谓。...
    小之丶閱讀 865評(píng)論 0 10
  • 從三月份找實(shí)習(xí)到現(xiàn)在斥季,面了一些公司训桶,掛了不少累驮,但最終還是拿到小米酣倾、百度、阿里谤专、京東躁锡、新浪、CVTE置侍、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,194評(píng)論 11 349
  • 今天很榮幸參加了去哪兒的一面映之,但是死的很慘,怪自己沒(méi)好好準(zhǔn)備蜡坊,但是收獲很大杠输,畢竟是第一次面試,以后要漲點(diǎn)心秕衙。 第一...
    Vaiety閱讀 296評(píng)論 0 0
  • 一蠢甲、啟用和禁用selinux 二、文件標(biāo)簽更改 三据忘、端口標(biāo)簽更改 四鹦牛、布爾值的狀態(tài)(0、1) 五勇吊、查日志 六曼追、se...
    Miracle001閱讀 1,262評(píng)論 0 3