溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Java的Volatile關(guān)鍵字的作用和實(shí)現(xiàn)原理

發(fā)布時(shí)間:2021-09-09 12:06:40 來(lái)源:億速云 閱讀:191 作者:chen 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Java的Volatile關(guān)鍵字的作用和實(shí)現(xiàn)原理”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java的Volatile關(guān)鍵字的作用和實(shí)現(xiàn)原理”吧!

volatile作用

volatile在并發(fā)編程中扮演著重要的角色,volatile是輕量級(jí)的synchronized,volatile關(guān)鍵字有兩個(gè)作用:

1)保證共享變量的可見(jiàn)性

可見(jiàn)性的意思是當(dāng)一個(gè)線程修改一個(gè)共享變量時(shí),另外一個(gè)線程能讀到這個(gè)修改的值。筆者此前一篇文章Java并發(fā)編程:Java內(nèi)存模型JMM中有說(shuō)到,Java內(nèi)存模型中有主內(nèi)存和本地內(nèi)存之分,本地內(nèi)存持有共享變量的一份副本,線程對(duì)共享變量的修改是先修改本地內(nèi)存的副本,然后再回寫(xiě)到主內(nèi)存中去。

可能存在這樣的情況,線程A和線程B同時(shí)去修改一個(gè)共享變量C,假設(shè)線程A先對(duì)共享變量C做了修改,而此時(shí)線程B卻沒(méi)能及時(shí)感知到共享變量C已經(jīng)發(fā)生了改變,緊接著B(niǎo)對(duì)本地過(guò)期的副本數(shù)據(jù)進(jìn)行了修改,這造成了共享變量的不可見(jiàn)問(wèn)題。

而使用了volatile關(guān)鍵字修改的共享變量,當(dāng)線程修改了共享變量之后,會(huì)立馬刷新到主內(nèi)存中,并且會(huì)使其他線程緩存了該地址的數(shù)據(jù)失效,這就保證了線程之間共享變量的可見(jiàn)性。

2)防止指令重排序

volatile關(guān)鍵字的另外一個(gè)作用就是防止指令重排序。代碼在實(shí)際執(zhí)行過(guò)程中,并不全是按照編寫(xiě)的順序進(jìn)行執(zhí)行的,在保證單線程執(zhí)行結(jié)果不變的情況下,編譯器或者CPU可能會(huì)對(duì)指令進(jìn)行重排序,以提高程序的執(zhí)行效率。但是在多線程的情況下,指令重排序可能會(huì)造成一些問(wèn)題,最常見(jiàn)的就是雙重校驗(yàn)鎖單例模式:

public class SingletonSafe {     private static volatile SingletonSafe singleton;     private SingletonSafe() {   }     public static SingletonSafe getSingleton() {        if (singleton == null) {            synchronized (SingletonSafe.class) {                if (singleton == null) {                    singleton = new SingletonSafe();               }           }       }        return singleton;   } }

如果沒(méi)有使用volatile關(guān)鍵字,則可能會(huì)出現(xiàn)其他線程獲取了一個(gè)未初始化完成的singleton對(duì)象,具體原因筆者不在這里贅述了,有興趣的同學(xué)可以搜索一下“double  checked locking with delay initialization”學(xué)習(xí)下,筆者后續(xù)有時(shí)間再寫(xiě)篇文章分析下。

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

1)可見(jiàn)性實(shí)現(xiàn)原理

對(duì)于volatile關(guān)鍵字修飾的變量,當(dāng)對(duì)volatile變量進(jìn)行寫(xiě)操作的時(shí)候,JVM會(huì)向處理器發(fā)送一條lock前綴的指令,將這個(gè)緩存中的變量回寫(xiě)到系統(tǒng)主存中。但是就算寫(xiě)回到內(nèi)存,如果其他處理器緩存的值還是舊的,再執(zhí)行計(jì)算操作就會(huì)有問(wèn)題,所以在多處理器下,為了保證各個(gè)處理器的緩存是一致的,就會(huì)實(shí)現(xiàn)緩存一致性協(xié)議。

緩存一致性協(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ì)強(qiáng)制重新從系統(tǒng)內(nèi)存里把數(shù)據(jù)讀到處理器緩存里。

所以,如果一個(gè)變量被volatile所修飾的話(huà),在每次數(shù)據(jù)變化之后,其值都會(huì)被強(qiáng)制刷入主存。而其他處理器的緩存由于遵守了緩存一致性協(xié)議,也會(huì)把這個(gè)變量的值從主存加載到自己的緩存中。這就保證了一個(gè)volatile在并發(fā)編程中,其值在多個(gè)緩存中是可見(jiàn)的。

2)防止指令重排序?qū)崿F(xiàn)原理

volatile防止指令重排序是通過(guò)內(nèi)存屏障來(lái)實(shí)現(xiàn)的。內(nèi)存屏障分為如下三種:

Store Barrier

Store屏障,是x86的”sfence“指令,強(qiáng)制所有在store屏障指令之前的store指令,都在該store屏障指令執(zhí)行之前被執(zhí)行。

Load Barrier

Load屏障,是x86上的”ifence“指令,強(qiáng)制所有在load屏障指令之后的load指令,都在該load屏障指令執(zhí)行之后被執(zhí)行

Full Barrier

Full屏障,是x86上的”mfence“指令,復(fù)合了load和save屏障的功能。

Java內(nèi)存模型中volatile變量在寫(xiě)操作之后會(huì)插入一個(gè)store屏障,在讀操作之前會(huì)插入一個(gè)load屏障。一個(gè)類(lèi)的final字段會(huì)在初始化后插入一個(gè)store屏障,來(lái)確保final字段在構(gòu)造函數(shù)初始化完成并可被使用時(shí)可見(jiàn)。也正是JMM在volatile變量讀寫(xiě)前后都插入了內(nèi)存屏障指令,進(jìn)而保證了指令的順序執(zhí)行。

到此,相信大家對(duì)“Java的Volatile關(guān)鍵字的作用和實(shí)現(xiàn)原理”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI