雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?-創(chuàng)新互聯(lián)

前言

從Java內(nèi)存模型出發(fā),結(jié)合并發(fā)編程中的原子性、可見(jiàn)性、有序性三個(gè)角度分析volatile所起的作用,并從匯編角度大致說(shuō)了volatile的原理,說(shuō)明了該關(guān)鍵字的應(yīng)用場(chǎng)景;在這補(bǔ)充一點(diǎn),分析下volatile是怎么在單例模式中避免雙檢鎖出現(xiàn)的問(wèn)題的。

成都創(chuàng)新互聯(lián)專注于網(wǎng)站建設(shè)|成都網(wǎng)站維護(hù)公司|優(yōu)化|托管以及網(wǎng)絡(luò)推廣,積累了大量的網(wǎng)站設(shè)計(jì)與制作經(jīng)驗(yàn),為許多企業(yè)提供了網(wǎng)站定制設(shè)計(jì)服務(wù),案例作品覆蓋成都水電改造等行業(yè)。能根據(jù)企業(yè)所處的行業(yè)與銷售的產(chǎn)品,結(jié)合品牌形象的塑造,量身策劃品質(zhì)網(wǎng)站。

并發(fā)編程的3個(gè)條件

1、原子性:要實(shí)現(xiàn)原子性方式較多,可用synchronized、lock加鎖,AtomicInteger等,但volatile關(guān)鍵字是無(wú)法保證原子性的;
2、可見(jiàn)性:要實(shí)現(xiàn)可見(jiàn)性,也可用synchronized、lock,volatile關(guān)鍵字可用來(lái)保證可見(jiàn)性;
3、有序性:要避免指令重排序,synchronized、lock作用的代碼塊自然是有序執(zhí)行的,volatile關(guān)鍵字有效的禁止了指令重排序,實(shí)現(xiàn)了程序執(zhí)行的有序性;

雙重檢查鎖定模式

雙重檢查鎖定(Double check locked)模式經(jīng)常會(huì)出現(xiàn)在一些框架源碼中,目的是為了延遲初始化變量。這個(gè)模式還可以用來(lái)創(chuàng)建單例。下面來(lái)看一個(gè) Spring 中雙重檢查鎖定的例子。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

這個(gè)例子中需要將配置文件加載到 handlerMappings中,由于讀取資源比較耗時(shí),所以將動(dòng)作放到真正需要 handlerMappings的時(shí)候。我們可以看到 handlerMappings前面使用了volatile。有沒(méi)有想過(guò)為什么一定需要 volatile?雖然之前了解了雙重檢查鎖定模式的原理,但是卻忽略變量使用了 volatile。
下面我們就來(lái)看下這背后的原因。

錯(cuò)誤的延遲初始化例子

想到延遲初始化一個(gè)變量,最簡(jiǎn)單的例子就是取出變量進(jìn)行判斷。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

這個(gè)例子在單線程環(huán)境可以正常運(yùn)行,但是在多線程環(huán)境就有可能會(huì)拋出空指針異常。為了防止這種情況,我們需要在該方法上使用 synchronized。這樣該方法在多線程環(huán)境就是安全的,但是這么做就會(huì)導(dǎo)致每次方法調(diào)用都需要獲取與釋放鎖,開(kāi)銷很大。
深入分析可以得知只有在初始化的變量的需要真正加鎖,一旦初始化之后,直接返回對(duì)象即可。
所以我們可以將該方法改造以下的樣子。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

這個(gè)方法首先判斷變量是否被初始化,沒(méi)有被初始化,再去獲取鎖。獲取鎖之后,再次判斷變量是否被初始化。第二次判斷目的在于有可能其他線程獲取過(guò)鎖,已經(jīng)初始化改變量。第二次檢查還未通過(guò),才會(huì)真正初始化變量。
這個(gè)方法檢查判定兩次,并使用鎖,所以形象稱為雙重檢查鎖定模式。
這個(gè)方案縮小鎖的范圍,減少鎖的開(kāi)銷,看起來(lái)很完美。然而這個(gè)方案有一些問(wèn)題卻很容易被忽略。

new 實(shí)例背后的指令

這個(gè)被忽略的問(wèn)題在于 Cache cache=new Cache()這行代碼并不是一個(gè)原子指令。使用 javap -c指令,可以快速查看字節(jié)碼。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

從字節(jié)碼可以看到創(chuàng)建一個(gè)對(duì)象實(shí)例,可以分為三步:
分配對(duì)象內(nèi)存
調(diào)用構(gòu)造器方法,執(zhí)行初始化
將對(duì)象引用賦值給變量。
虛擬機(jī)實(shí)際運(yùn)行時(shí),以上指令可能發(fā)生重排序。以上代碼 2,3 可能發(fā)生重排序,但是并不會(huì)重排序 1 的順序。也就是說(shuō) 1 這個(gè)指令都需要先執(zhí)行,因?yàn)?2,3 指令需要依托 1 指令執(zhí)行結(jié)果。
Java 語(yǔ)言規(guī)規(guī)定了線程執(zhí)行程序時(shí)需要遵守 intra-thread semantics。intra-thread semantics保證重排序不會(huì)改變單線程內(nèi)的程序執(zhí)行結(jié)果。這個(gè)重排序在沒(méi)有改變單線程程序的執(zhí)行結(jié)果的前提下,可以提高程序的執(zhí)行性能。
雖然重排序并不影響單線程內(nèi)的執(zhí)行結(jié)果,但是在多線程的環(huán)境就帶來(lái)一些問(wèn)題。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

上面錯(cuò)誤雙重檢查鎖定的示例代碼中,如果線程 1 獲取到鎖進(jìn)入創(chuàng)建對(duì)象實(shí)例,這個(gè)時(shí)候發(fā)生了指令重排序。當(dāng)線程1 執(zhí)行到 t3 時(shí)刻,線程 2 剛好進(jìn)入,由于此時(shí)對(duì)象已經(jīng)不為 Null,所以線程 2 可以自由訪問(wèn)該對(duì)象。然后該對(duì)象還未初始化,所以線程 2 訪問(wèn)時(shí)將會(huì)發(fā)生異常。

volatile 作用

正確的雙重檢查鎖定模式需要需要使用 volatile。volatile主要包含兩個(gè)功能。
保證可見(jiàn)性。使用 volatile定義的變量,將會(huì)保證對(duì)所有線程的可見(jiàn)性。
禁止指令重排序優(yōu)化。
由于 volatile禁止對(duì)象創(chuàng)建時(shí)指令之間重排序,所以其他線程不會(huì)訪問(wèn)到一個(gè)未初始化的對(duì)象,從而保證安全性。
注意,volatile禁止指令重排序在 JDK 5 之后才被修復(fù)

使用局部變量?jī)?yōu)化性能

重新查看 Spring 中雙重檢查鎖定代碼。
雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?

可以看到方法內(nèi)部使用局部變量,首先將實(shí)例變量值賦值給該局部變量,然后再進(jìn)行判斷。最后內(nèi)容先寫入局部變量,然后再將局部變量賦值給實(shí)例變量。
使用局部變量相對(duì)于不使用局部變量,可以提高性能。主要是由于 volatile變量創(chuàng)建對(duì)象時(shí)需要禁止指令重排序,這就需要一些額外的操作。

總結(jié)

對(duì)象的創(chuàng)建可能發(fā)生指令的重排序,使用 volatile可以禁止指令的重排序,保證多線程環(huán)境內(nèi)的系統(tǒng)安全。

最后

歡迎大家一起交流,喜歡文章記得點(diǎn)個(gè)贊喲,感謝支持!

創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國(guó)云服務(wù)器,動(dòng)態(tài)BGP最優(yōu)骨干路由自動(dòng)選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡(luò)助力業(yè)務(wù)部署。公司持有工信部辦法的idc、isp許可證, 機(jī)房獨(dú)有T級(jí)流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確進(jìn)行流量調(diào)度,確保服務(wù)器高可用性。佳節(jié)活動(dòng)現(xiàn)已開(kāi)啟,新人活動(dòng)云服務(wù)器買多久送多久。

網(wǎng)頁(yè)標(biāo)題:雙重檢查鎖單例模式為什么要用volatile關(guān)鍵字?-創(chuàng)新互聯(lián)
URL地址:http://muchs.cn/article18/eehdp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈移動(dòng)網(wǎng)站建設(shè)、定制開(kāi)發(fā)ChatGPT、手機(jī)網(wǎng)站建設(shè)自適應(yīng)網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運(yùn)營(yíng)