av一区二区在线观看_亚洲男人的天堂网站_日韩亚洲视频_在线成人免费_欧美日韩精品免费观看视频_久草视

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

談?wù)剬?duì)Java中的volatile的理解

瀏覽:75日期:2022-08-21 14:27:25

前言

volatile相關(guān)的知識(shí)其實(shí)自己一直都是有掌握的,能大概講出一些知識(shí),例如:它可以保證可見(jiàn)性;禁止指令重排。這兩個(gè)特性張口就來(lái),但要再往深了問(wèn),具體是如何實(shí)現(xiàn)這兩個(gè)特性的,以及在什么場(chǎng)景下使用volatile,為什么不直接用synchronized這種深入和擴(kuò)展相關(guān)的問(wèn)題,就回答的不好了。因?yàn)関olatile是面試必問(wèn)的知識(shí),所以這次準(zhǔn)備把這部分知識(shí)也給啃掉。

系統(tǒng)處理效率與Java內(nèi)存模型

在計(jì)算機(jī)中,每條程序指令都是在CPU中執(zhí)行的,而CPU執(zhí)行指令的數(shù)據(jù)都是臨時(shí)存儲(chǔ)在內(nèi)存中的,但是CPU的執(zhí)行速度遠(yuǎn)超內(nèi)存的讀取速度,如果所有的CPU指令都是通過(guò)內(nèi)存來(lái)讀取數(shù)據(jù)的話(huà)那么將大大的降低了系統(tǒng)的處理效率,所以現(xiàn)代計(jì)算機(jī)系統(tǒng)都不得不加入一層或多層讀寫(xiě)速度盡可能接近處理器運(yùn)算速度的高速緩存(Cache)來(lái)作為內(nèi)存與處理器之間的緩沖。

將運(yùn)算需要使用的數(shù)據(jù)復(fù)制到緩存中,讓運(yùn)算能快速進(jìn)行,當(dāng)運(yùn)算結(jié)束后,在從緩存同步回內(nèi)存之中,這樣處理器就無(wú)須等待緩慢的內(nèi)存讀寫(xiě)了。

雖然說(shuō)增加了高速緩存提高了CPU的處理效率,但是也帶來(lái)了新的問(wèn)題 :

現(xiàn)代計(jì)算機(jī)都是多核CPU,一開(kāi)始,內(nèi)存中的變量A的值是1,第一個(gè)CPU讀取了數(shù)據(jù),第二個(gè)CPU也將數(shù)據(jù)讀取到了自己的高速緩存當(dāng)中,當(dāng)?shù)谝粋€(gè)CPU對(duì)變量A進(jìn)行加1操作時(shí),變量A的值變成了2,然后將將變量A的值寫(xiě)回內(nèi)存中,這時(shí)第二個(gè)CPU也對(duì)變量A進(jìn)行加1操作時(shí),由于第二個(gè)CPU中高速緩存中的值還是1,所以加1操作后的結(jié)果為2,然后第二個(gè)CPU又將變量A的值同步回內(nèi)存中,這樣就導(dǎo)致執(zhí)行了兩次加1操作后,變量A的值最終是2,而不是3。這種被多個(gè)CPU訪問(wèn)的變量,通常稱(chēng)為共享變量。而產(chǎn)生的上面的問(wèn)題,就是引入了高速緩存后的,主內(nèi)存和緩存內(nèi)容不一致的問(wèn)題。因?yàn)槊總€(gè)處理器有自己的高速緩存,但是它們又共享同一塊主內(nèi)存,所以必然會(huì)出現(xiàn)主內(nèi)存不知該以哪個(gè)高速緩存中的變量為準(zhǔn)的情況。

談?wù)剬?duì)Java中的volatile的理解

上面這個(gè)緩存不一致的問(wèn)題,我們先記下來(lái),繼續(xù)來(lái)看Java內(nèi)存模型,其實(shí)Java內(nèi)存模型描述的上面講的計(jì)算機(jī)系統(tǒng)高速緩存和內(nèi)存之間的關(guān)系類(lèi)似。

Java內(nèi)存模型描述了,各種變量的訪問(wèn)規(guī)則,以及將變量存儲(chǔ)到內(nèi)存和從內(nèi)存讀取變量的這種底層細(xì)節(jié)。

在Java內(nèi)存模型中關(guān)注的變量都是共享變量(實(shí)例變量、類(lèi)變量)。所有的共享變量都是存儲(chǔ)在主內(nèi)存中的,但是每個(gè)線程在訪問(wèn)變量的時(shí)候也都會(huì)在自己的工作內(nèi)存(處理器高速緩存)中保留一份共享變量的副本。

Java內(nèi)存模型(Java Memory Model,簡(jiǎn)稱(chēng)JMM)規(guī)定:

線程對(duì)變量的所有操作(讀,寫(xiě))都必須在工作內(nèi)存中進(jìn)行,不能直接操作主內(nèi)存中的數(shù)據(jù)。不同線程之間 也不能直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間的變量值傳遞必須通過(guò)主內(nèi)存進(jìn)行中轉(zhuǎn)傳遞。在JMM中工作內(nèi)存和主內(nèi)存的關(guān)系如下圖:

談?wù)剬?duì)Java中的volatile的理解

Volatile的可見(jiàn)性(保證立即可見(jiàn))

繼續(xù)我們上面的緩存一致性的問(wèn)題,這個(gè)問(wèn)題,在Java內(nèi)存模型中,就是可見(jiàn)性的問(wèn)題,即一個(gè)線程修改了共享變量的值,對(duì)另一個(gè)線程來(lái)說(shuō)是不是立即可見(jiàn)的。如果不是立即可見(jiàn)的,那么就會(huì)出現(xiàn)緩存一致性的問(wèn)題,如果是立即可見(jiàn)的,那么另一個(gè)線程在進(jìn)行操作的時(shí)候,拿到的變量值就是最新的。就可以解決可見(jiàn)性的問(wèn)題。

那么怎么解決可見(jiàn)性問(wèn)題呢?

方案一:加鎖

將共享變量加鎖,無(wú)論是synchronized還是Lock都可以,加鎖達(dá)到的目的是在同一時(shí)間內(nèi)只能有一個(gè)線程能對(duì)共享變量進(jìn)行操作,就是說(shuō),共享變量從讀取到工作內(nèi)存到更新值后,同步回主內(nèi)存的過(guò)程中,其他線程是操作不了這個(gè)變量的。這樣自然就解決了可見(jiàn)性的問(wèn)題了,但是這樣的效率比較低,操作不了共享變量的線程就只能阻塞。

方案二:volatile修飾修飾共享變量

當(dāng)一個(gè)共享變量被volatile修飾后,會(huì)保證每個(gè)線程將變量修改后的值立即同步回主內(nèi)存中,當(dāng)其他線程有需要讀取變量時(shí)會(huì)讀取到最新的變量值。

那么volatile做了些什么操作就能解決可見(jiàn)性的問(wèn)題呢?

被volatile修飾的變量,在被線程操作時(shí),會(huì)有這樣的機(jī)制:

就是線程對(duì)變量操作時(shí)會(huì)從主內(nèi)存中讀取到自己的工作內(nèi)存中,當(dāng)線程對(duì)變量進(jìn)行了修改后,那么其他已經(jīng)讀取了此變量的線程中的變量副本就會(huì)失效,這樣其他線程在使用變量的時(shí)候,發(fā)現(xiàn)已經(jīng)失效,那么就會(huì)去主內(nèi)存中重新獲取,這樣獲取到的就只最新的值了。

那么volatile這個(gè)關(guān)鍵字是如何實(shí)現(xiàn)這套機(jī)制的呢?

因?yàn)橐慌_(tái)計(jì)算機(jī)有多臺(tái)CPU,同一個(gè)變量,在多個(gè)CPU中緩存的值有可能不一樣,那么以誰(shuí)緩存的值為準(zhǔn)呢?

既然大家都有自己的值,那么各個(gè)CPU間就產(chǎn)生了一種協(xié)議,來(lái)保證按照一定的規(guī)律為準(zhǔn),來(lái)確定共享變量的準(zhǔn)確值,這樣各個(gè)CPU在讀寫(xiě)共享變量時(shí)都按照協(xié)議來(lái)操作。

這就是緩存一致性協(xié)議。

最著名的緩存一致性協(xié)議就是Intel的MESI了,說(shuō)MESI時(shí),先解釋一下,緩存行:

緩存行(cache line):CPU高速緩存的中可以分配的最小存儲(chǔ)單位,高速緩存中的變量都是存在緩存行中的。

MESI的核心思想就是,當(dāng)CPU對(duì)變量進(jìn)行寫(xiě)操作時(shí)發(fā)現(xiàn),變量是共享變量,那么就會(huì)通知其他CPU中將該變量的緩存行設(shè)置為無(wú)效狀態(tài)。當(dāng)其他CPU在操作變量時(shí)發(fā)現(xiàn)此變量在的緩存行已經(jīng)無(wú)效,那么就會(huì)去主內(nèi)存中重新讀取最新的變量。

那么其他CPU是如何發(fā)現(xiàn)變量被修改了的呢?

因?yàn)镃PU和其他部件的進(jìn)行通信是通過(guò)總線來(lái)進(jìn)行的,所以每個(gè)CPU通過(guò)嗅探總線上的傳播數(shù)據(jù),來(lái)檢查自己緩存的值是不是過(guò)期了,當(dāng)處理器發(fā)現(xiàn)自己換成行對(duì)應(yīng)的內(nèi)存地址被修改后,就會(huì)將自己工作內(nèi)存中的緩存行設(shè)置成無(wú)須狀態(tài),當(dāng)CPU對(duì)此變量進(jìn)行修改時(shí)會(huì)重新從系統(tǒng)主內(nèi)存中讀取變量。

談?wù)剬?duì)Java中的volatile的理解

Volatile的有序性(禁止指令重排)

一般來(lái)說(shuō),我們寫(xiě)程序的時(shí)候,都是要把先代碼從上往下寫(xiě),默認(rèn)的認(rèn)為程序是自頂向下順序執(zhí)行的,但是CPU為了提高效率,在保證最終結(jié)果準(zhǔn)確的情況下,是會(huì)對(duì)指令進(jìn)行重新排序的。就是說(shuō)寫(xiě)在前的代碼不一定先執(zhí)行,在后面的也不一定晚執(zhí)行。

舉個(gè)例子:

int a = 5; // 代碼1int b = 8; // 代碼2a = a + 4;// 代碼3int c = a + b;// 代碼4

上面四行代碼的執(zhí)行順序有可能是

談?wù)剬?duì)Java中的volatile的理解

JMM在是允許指令重排序的,在保證最后結(jié)果正確的情況下,處理器可以盡情的發(fā)揮,提高執(zhí)行效率。

當(dāng)多個(gè)線程執(zhí)行代碼的時(shí)候重排序的情況就更為突出了,各個(gè)CPU為了提高自己的效率,有可能會(huì)產(chǎn)生競(jìng)爭(zhēng)情況,這樣就有可能導(dǎo)致最終執(zhí)行的正確性。

所以為了保證在多個(gè)線程下最終執(zhí)行的正確性,將變量用volatile進(jìn)行修飾,這樣就會(huì)達(dá)到禁止指令重排序的效果(其實(shí)也可以通過(guò)加鎖,還有一些其他已知規(guī)則來(lái)實(shí)現(xiàn)禁止指令重排序,但是我們這里只討論volatile的實(shí)現(xiàn)方式)。

那么volatile是如何實(shí)現(xiàn)指令重排序的呢?

答案是:內(nèi)存屏障

內(nèi)存屏障是一組CPU指令,用于實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制。Java編譯器,會(huì)在生成指令系列時(shí),在適當(dāng)?shù)奈恢脮?huì)插入內(nèi)存屏障來(lái)禁止處理器對(duì)指令的重新排序。

volatile會(huì)在變量寫(xiě)操作的前后加入兩個(gè)內(nèi)存屏障,來(lái)保證前面的寫(xiě)指令和后面的讀指令是有序的。

談?wù)剬?duì)Java中的volatile的理解

volatile在變量的讀操作后面插入兩個(gè)指令,禁止后面的讀指令和寫(xiě)指令重排序。

談?wù)剬?duì)Java中的volatile的理解

有序性,不僅只有volatile能保證,其他的實(shí)現(xiàn)方式也能保證,但是如果每一種實(shí)現(xiàn)方式都要了解那對(duì)于開(kāi)發(fā)人員來(lái)說(shuō)就比較困難了。

所以從JDK5就出現(xiàn)了happen-before原則,也叫先行發(fā)生原則。先行發(fā)生原則總結(jié)起來(lái)就是:如果一個(gè)操作A的產(chǎn)生的影響能被另一個(gè)操作B觀察到,那么可以說(shuō),這個(gè)操作A先行發(fā)生與操作B。

這里所說(shuō)的影響包括內(nèi)存中的變量的修改,調(diào)用了方法,發(fā)送量消息等。

volatile中的先行發(fā)生原則是,對(duì)一個(gè)volatile變量的寫(xiě)操作,先行發(fā)生于后面任何地方對(duì)這個(gè)變量的讀操作。

Volatile無(wú)法保證原子性

原子性,是指一個(gè)操作過(guò)程要么都成功,要么都失敗,是一個(gè)獨(dú)立的完整的。

就像上面說(shuō)的,如果多個(gè)線程對(duì)一個(gè)變量進(jìn)行累加,那么肯定得不到想要的結(jié)果,因?yàn)槔奂泳筒皇且粋€(gè)原子操作。

要保證累加最終結(jié)果正確,要么對(duì)累加變量加鎖,要么就用AotomicInteger這樣的變量。

/** * 雙重檢查加鎖式單例 */public class DoubleCheckLockSingleton implements Serializable{ /** * 靜態(tài)變量,用來(lái)存放實(shí)例。 */ private volatile static DoubleCheckLockSingleton doubleCheckLockSingleton = null; /** * 私有化構(gòu)造方法,禁止外部創(chuàng)建實(shí)例。 */ private DoubleCheckLockSingleton(){} /** * 雙重檢查加鎖的方式保證線程安全又能獲得到唯一實(shí)例 * @return */ public static DoubleCheckLockSingleton getInstance(){ //第一次檢查實(shí)例是否已經(jīng)存在,不存在則進(jìn)入代碼塊 if(null == doubleCheckLockSingleton){ synchronized (DoubleCheckLockSingleton.class){//第二次檢查if(null==doubleCheckLockSingleton){ doubleCheckLockSingleton = new DoubleCheckLockSingleton();} } } return doubleCheckLockSingleton; }}

為什么要進(jìn)行雙重檢查呢?

當(dāng)?shù)谝粋€(gè)線程走到第一次檢查時(shí)發(fā)現(xiàn)對(duì)象為空,然后進(jìn)入鎖,第二次就檢查時(shí)也為空,那么就去創(chuàng)建對(duì)象,但是這個(gè)時(shí)候又來(lái)了一個(gè)線程來(lái)到了第一次檢查,發(fā)現(xiàn)為空,但是這個(gè)時(shí)候因?yàn)殒i被占用,所以就只能阻塞等待,然后第一個(gè)線程創(chuàng)建對(duì)象成功了,由于對(duì)象是被volatile修飾的能夠立即反饋到其他線程上,所以在第一個(gè)線程釋放鎖之后,第二個(gè)線程進(jìn)入了鎖,然后進(jìn)行第二次檢查時(shí),發(fā)現(xiàn)對(duì)象已經(jīng)被創(chuàng)建了,那么就不在創(chuàng)建對(duì)象了。從而保證的單例。

還有就是如果創(chuàng)建對(duì)象,步驟:

分配內(nèi)存空間。 調(diào)用構(gòu)造器,實(shí)例化。 返回內(nèi)存地址給引用。

如果這三個(gè)指令順序被重排了,那么當(dāng)多線程來(lái)獲取對(duì)象的時(shí)候就會(huì)造成對(duì)象雖然實(shí)例化了,但是沒(méi)有分配內(nèi)存空間,會(huì)有空指針的風(fēng)險(xiǎn)。所以加上了volatile的對(duì)象,也保證了在第二次檢查時(shí)不會(huì)被已經(jīng)在創(chuàng)建過(guò)程中的對(duì)象有被檢測(cè)為空的風(fēng)險(xiǎn)。

總結(jié)一下

volatile其實(shí)可以看作是輕量級(jí)的synchronized,雖然說(shuō)volatile不能保證原子性,但是如果在多線程下的操作本身就是原子性操作(例如賦值操作),那么使用volatile會(huì)由于synchronized。

volatile可以適用于,某個(gè)標(biāo)識(shí)flag,一旦被修改了就需要被其他線程立即可見(jiàn)的情況。也可以修飾作為觸發(fā)器的變量,一旦變量被任何一個(gè)線程修改了,就去觸發(fā)執(zhí)行某個(gè)操作。

volatile的變量寫(xiě)操作happen-before,后面任何對(duì)此volatile變量的讀操作。

到此這篇關(guān)于談?wù)剬?duì)Java中的volatile的理解的文章就介紹到這了,更多相關(guān)Java中的volatile內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 一区二区精品 | 99re视频在线 | 欧美综合一区二区 | 91麻豆精品国产91久久久久久 | 中文字幕国产日韩 | 日韩精彩视频 | 中文字幕在线网 | 涩爱av一区二区三区 | 亚洲成人免费av | 亚洲视频二区 | 在线观看亚洲精品视频 | av在线一区二区三区 | 午夜电影网站 | 一区二区三区视频在线 | 亚洲日韩中文字幕一区 | 欧美日韩高清一区二区三区 | 国产精品美女久久久久久免费 | 久久久久国产一区二区三区 | 欧美激情精品久久久久久变态 | 一区二区三区四区日韩 | 99国产精品视频免费观看一公开 | 午夜电影网站 | 一区二区在线免费观看视频 | 国产福利在线 | 国产精品不卡一区 | 成人久久久 | 国产精品永久久久久久久www | 久久久精品一区二区三区四季av | 中文字幕免费中文 | 一级a爱片久久毛片 | 波多野结衣亚洲 | 九色91视频| 在线观看av网站永久 | 97超级碰碰 | 欧美黄色网 | 久久久久国产精品www | 欧美在线观看一区二区 | 香蕉一区 | 新91视频网 | 免费成年网站 | 欧美性区 |