Java并發(fā)系列之volatile

講到Java并發(fā),多線程編程,一定避免不了對(duì)關(guān)鍵字volatile的了解,那么如何來(lái)認(rèn)識(shí)volatile,從哪些方面來(lái)了解它會(huì)比較合適呢?

個(gè)人認(rèn)為,既然是多線程編程,那我們?cè)谄匠5膶W(xué)習(xí)中,工作中,大部分都接觸到的就是線程安全的概念。

而線程安全就會(huì)涉及到共享變量的概念,所以首先,我們得弄清楚共享變量是什么,且處理器和內(nèi)存間的數(shù)據(jù)交互機(jī)制是如何導(dǎo)致共享變量變得不安全。

共享變量

能夠在多個(gè)線程間被多個(gè)線程都訪問(wèn)到的變量,我們稱之為共享變量。共享變量包括所有的實(shí)例變量,靜態(tài)變量和數(shù)組元素。他們都被存放在堆內(nèi)存中。

處理器與內(nèi)存的通信機(jī)制

大家都知道處理器是用來(lái)做計(jì)算的,且速度是非??斓模鴥?nèi)存是用來(lái)存儲(chǔ)數(shù)據(jù)的,且其訪問(wèn)速度相比處理器來(lái)說(shuō),是慢了好幾個(gè)級(jí)別的。那么當(dāng)處理器需要處理數(shù)據(jù)時(shí),如果每次都直接從內(nèi)存拿數(shù)據(jù)的話,就會(huì)導(dǎo)致效率非常低,因此在現(xiàn)代計(jì)算機(jī)系統(tǒng)中,處理器是不直接跟內(nèi)存通信的,而是在處理器和內(nèi)存之間設(shè)置了多個(gè)緩存,也就是我們常常聽到的L1, L2, L3等高速緩存。

具體架構(gòu)如下所示:


memory_processor_communication.png

處理器都是將數(shù)據(jù)從內(nèi)存讀到自己內(nèi)部的緩存中,然后在緩存中對(duì)數(shù)據(jù)進(jìn)行修改等操作,結(jié)束后再由緩存寫到回主存中去。
如果一個(gè)共享變量 X,在多線程的情況下,同時(shí)被多個(gè)處理器讀到各自的緩存中去,當(dāng)其中一個(gè)處理器修改了X的值,改成Y了,先寫回了內(nèi)存,而此時(shí)另外一個(gè)處理器,又將X改成Z,再寫回內(nèi)存,那么之前的Y就會(huì)被覆蓋掉了。

這種情況下,數(shù)據(jù)就已經(jīng)有問(wèn)題了,這種因?yàn)槎嗑€程操作而導(dǎo)致的異常問(wèn)題,通常我們就叫做線程不安全。

memory_processor_communication_core1.png

memory_processor_communication_core2.png

如上述兩圖所示,X的變量同時(shí)被不同的處理器修改成各自的Y和Z,那么如何避免這種情況呢?
這就涉及到了Java內(nèi)存模型中的可見性的概念。

Java內(nèi)存模型之可見性

可見性,意思就是說(shuō),在多線程編程中,某個(gè)共享變量在其中一個(gè)線程被修改了,其修改結(jié)果要馬上能夠被其他線程看到,拿上面的例子來(lái)說(shuō),也就是當(dāng)X在其中一個(gè)處理器的緩存中被修改成Y了, 另一個(gè)處理器必須能夠馬上知道自己緩存中的X已經(jīng)被修改成Y了,當(dāng)此處理器要拿此變量去參與計(jì)算的時(shí)候,必須重新去內(nèi)存中將此變量的值Y讀到緩存中。

而一個(gè)變量,如果被聲明成violate,那么其就能保證這種可見性,這就是volatile變量的作用了。

volatile

那么 volatile 變量能夠保證可見性的實(shí)現(xiàn)原理是什么?
聲明成volatile的變量,在編譯成匯編指令的時(shí)候,會(huì)多出以下一行:

0x0bca13ae:lock addl $0x0,(%esp)      ;

這一句指令的意思是在寄存器上做一個(gè)+0的空操作,但這條指令有個(gè)Lock前綴。
而處理器在處理Lock前綴指令時(shí),其實(shí)是聲言了處理器的Lock#信號(hào)。
在之前的處理器中,Lock#信號(hào)會(huì)導(dǎo)致傳輸數(shù)據(jù)的總線被鎖定,其他處理器都不能訪問(wèn)總線,從而保證處理Lock指令的處理器能夠獨(dú)享操作數(shù)據(jù)所在的內(nèi)存區(qū)域。

但由于總線被鎖住,其他的處理器都被堵住了,影響多處理器執(zhí)行的效率。在后來(lái)的處理器中,聲言Lock#信號(hào)的處理器,不會(huì)再鎖住總線,而是檢查到數(shù)據(jù)所在的內(nèi)存區(qū)域,如果是在處理器的內(nèi)部緩存中,則會(huì)鎖定此緩存區(qū)域,將緩存寫回到內(nèi)存當(dāng)中,并利用緩存一致性的原則來(lái)保證其他處理器中的緩存區(qū)域數(shù)據(jù)的一致性。

緩存一致性

緩存一致性原則會(huì)保證一個(gè)在緩存中的數(shù)據(jù)被修改了,會(huì)保證其他緩存了此數(shù)據(jù)的處理器中的緩存失效,從而讓處理器重新去內(nèi)存中讀取最新修改后的數(shù)據(jù)。

在實(shí)際的處理器操作中,各個(gè)處理器會(huì)一直在總線上嗅探其內(nèi)部緩存區(qū)域中的內(nèi)存地址在其它處理器的操作情況,一旦嗅探到某處理器打算修改某內(nèi)存地址,而此內(nèi)存地址剛好也在自己內(nèi)部的緩存中,則會(huì)強(qiáng)制讓自己的緩存無(wú)效。當(dāng)下次訪問(wèn)此內(nèi)存地址的時(shí)候,則重新從內(nèi)存當(dāng)中讀取新數(shù)據(jù)。

volatile不僅保證了共享變量在多線程間的可見性,其還保證了一定的有序性。

有序性

何謂有序性呢?
事實(shí)上,java程序代碼在編譯器階段和處理器執(zhí)行階段,為了優(yōu)化執(zhí)行的效率,有可能會(huì)對(duì)指令進(jìn)行重排序。
如果一些指令彼此之間互相不影響,那么就有可能不按照代碼順序執(zhí)行,比如后面的代碼先執(zhí)行,而之前的代碼則慢執(zhí)行,但處理器會(huì)保證結(jié)束時(shí)的輸出結(jié)果是一致的。
以上的這種情況就說(shuō)明指令有可能不是有序的。

volatile變量,上面我們看過(guò)其匯編指令,會(huì)多出一條Lock前綴的指令,這條指令能夠 保證,在這條指令之前的所有指令全部執(zhí)行完畢,而在這條指令之后的所有指令全部未執(zhí)行,也相于在這里立起了一道柵欄,稱之為內(nèi)存柵欄,而更通俗的說(shuō)法,則是內(nèi)存屏障

那么有了這道屏障,volatile變量就禁止了指令的重排序,從而保證了指令執(zhí)行的有序性。

所有對(duì)volatile變量的讀操作一定發(fā)生在對(duì)volatile變量的寫操作之后。這同時(shí)也說(shuō)明了volatile變量在多個(gè)線程之間能夠?qū)崿F(xiàn)可見性的原理。所以各種規(guī)定和操作,其實(shí)之間互有關(guān)聯(lián),彼此依賴,才能更好地保證指令執(zhí)行的準(zhǔn)確和效率。

內(nèi)存屏障

在上面我們也引出了內(nèi)存屏障的概念,也知道了,其實(shí)它就是一組處理器的操作指令。

插入一個(gè)內(nèi)存屏障,則相當(dāng)于告訴處理器和編譯器先于這個(gè)指令的必須先執(zhí)行,后于這個(gè)指令的必須后執(zhí)行。

image

內(nèi)存屏障另一個(gè)作用是強(qiáng)制更新一次不同CPU的緩存。

例如,一個(gè)寫屏障會(huì)把這個(gè)屏障前寫入的數(shù)據(jù)刷新到緩存,這樣任何試圖讀取該數(shù)據(jù)的線程將得到最新值,而不用考慮到底是被哪個(gè)cpu核心或者哪顆CPU執(zhí)行的。

這再仔細(xì)一想,不就是上面所說(shuō)的volatile的作用嗎?

所以,內(nèi)存屏障,可見性,有序性,緩存一致性原則,在java并發(fā)中各種各樣的名詞,本質(zhì)上可能就只是同一種現(xiàn)象或者同一種設(shè)計(jì),從不同的角度觀察和探討所得出的不同的解釋。
下一篇文章
Java并發(fā)系列之synchronized

參考資料

聊聊并發(fā)(一)深入分析Volatile的實(shí)現(xiàn)原理

內(nèi)存屏障與JVM并發(fā)

Java并發(fā)編程:volatile關(guān)鍵字解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容