前言 上篇文章我們從硬件級(jí)別探索,對(duì)可見(jiàn)性和有序性的認(rèn)識(shí)上升了一個(gè)高度,卻遲遲沒(méi)有介紹原子性的解決方案。 今天我們就來(lái)聊一聊原子性的解決方案,鎖。 引入鎖機(jī)制,除了可以保證原子性,同時(shí)也可以保證可見(jiàn)性和有序性。 相信小伙伴們對(duì)于synchronized互斥鎖一定很熟悉,但是你懂它的實(shí)現(xiàn)原理嗎,今天就讓我們一起來(lái)揭開(kāi)它的神秘面紗吧。 synchronized的原子性 首先我們來(lái)看一下synchronized是怎么保證原子性的。 其實(shí)往最簡(jiǎn)單了解釋,還是比較容易理解的。synchronized加鎖主要靠的是monitor,monitor在java里可以理解成一個(gè)監(jiān)視器,在操作系統(tǒng)里它又被稱為管程。 簡(jiǎn)單的模型如下圖: 當(dāng)我們的程序通過(guò)synchronized鎖定一個(gè)對(duì)象的時(shí)候,這個(gè)對(duì)象會(huì)關(guān)聯(lián)一個(gè)monitor,獲取鎖時(shí)會(huì)對(duì)monitor中的計(jì)數(shù)器進(jìn)行+1操作,釋放鎖的時(shí)候進(jìn)行-1操作,同時(shí)也是支持可重入的,同一個(gè)線程再次獲取該對(duì)象的鎖,計(jì)數(shù)器就再+1。 如果計(jì)數(shù)器為0就代表完全釋放了鎖,其他線程可以獲取鎖。 如果線程調(diào)用了wait方法,會(huì)釋放鎖資源,同時(shí)把線程放入waitset中,等待notifyall方法喚醒,喚醒后重新開(kāi)始競(jìng)爭(zhēng)鎖資源。 這就是sychronized鎖的最簡(jiǎn)單的解釋,我們當(dāng)然不會(huì)滿足于此,接下來(lái)我們繼續(xù)深入研究一下。 先看一段代碼: MyLock lock = new MyLock();//一個(gè)自定義的鎖對(duì)象 java的對(duì)象在內(nèi)存中存儲(chǔ)的布局可以分為三塊區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)和對(duì)齊填充(Padding)。 對(duì)象頭包含Mark Word(包含hashcode、鎖數(shù)據(jù)、GC信息等)和Class MetaData Address(指向Class對(duì)象的指針)。 實(shí)例數(shù)據(jù)就是我們?cè)趯?duì)象里存放的那些數(shù)據(jù)。 java要求對(duì)象大小為8字節(jié)的整數(shù)倍,對(duì)齊填充就是用來(lái)填充字節(jié)的,沒(méi)有其他意義。 Mark Word會(huì)指向一個(gè)monitor,這個(gè)monitor是C++實(shí)現(xiàn)的一個(gè)Object Monitor對(duì)象,首先線程在獲取鎖時(shí),先進(jìn)入到entry list中,然后通過(guò)CAS對(duì)count計(jì)數(shù)器進(jìn)行+1操作,如果+1成功代表獲取到鎖,此時(shí)就會(huì)把該線程的信息放入owner中,owner就是用來(lái)存儲(chǔ)當(dāng)前獲取到鎖的線程的。整體結(jié)構(gòu)如圖所示:
sychronized的可見(jiàn)性 在說(shuō)可見(jiàn)性之前,我們先引入兩個(gè)概念:Store屏障和Load屏障。 Store屏障就是強(qiáng)制CPU執(zhí)行flush操作,Load屏障就是強(qiáng)制CPU執(zhí)行refresh操作。 flush和refresh我們上篇文章已經(jīng)說(shuō)過(guò),這里就不再解釋了。 那sychronized是如何實(shí)現(xiàn)可見(jiàn)性的呢,其實(shí)就是利用了內(nèi)存屏障。如下: sychronized(this){ sychronized的有序性 同樣在說(shuō)有序性之前引入兩個(gè)新的內(nèi)存屏障:Acquire屏障和Release屏障。 Acquire屏障可以禁止讀操作和其他讀寫(xiě)操作之間發(fā)生指令重排,Release屏障可以禁止寫(xiě)操作和其他讀寫(xiě)操作之間發(fā)生指令重排。 那sychronized是如何實(shí)現(xiàn)有序性的呢,其實(shí)就是利用了這兩個(gè)內(nèi)存屏障。如下: sychronized(this){ 需要注意的是Acquire屏障和Release屏障保證的是sychronized內(nèi)部的代碼不會(huì)與外部的代碼之間發(fā)生指令重排,內(nèi)部的代碼自己還是可能發(fā)生指令重排的。 sychronized的鎖優(yōu)化 jdk1.6后jvm對(duì)sychronized進(jìn)行了鎖優(yōu)化,這部分我們做個(gè)概念了解就可以了。 1.鎖消除 鎖消除是JIT編譯器對(duì)sychronized的優(yōu)化,在編譯的時(shí)候會(huì)通過(guò)逃逸分析技術(shù),來(lái)分析鎖對(duì)象。如果只有一個(gè)線程來(lái)加鎖和解鎖,沒(méi)有鎖競(jìng)爭(zhēng),那就沒(méi)有必要加鎖,會(huì)去掉monitorenter和monitorexit指令。 2.鎖粗化 這個(gè)意思是,如果有多個(gè)連續(xù)的加鎖釋放鎖操作,那么編譯后會(huì)變成一把鎖。 例如 sychronized(this){} sychronized(this){} sychronized(this){} 連著三個(gè)加鎖操作,編譯后會(huì)變成一個(gè)。 3.偏向鎖 偏向鎖主要是為了減少monitorenter和monitorexit指令的CAS操作,減少開(kāi)銷,如果認(rèn)為當(dāng)前鎖大概率只有一個(gè)線程來(lái)競(jìng)爭(zhēng),那么就會(huì)給這個(gè)鎖維護(hù)好一個(gè)偏好Bias,之后該線程加鎖和釋放鎖都通過(guò)這個(gè)Bias來(lái)執(zhí)行,不需要去執(zhí)行CAS了。 但是如果發(fā)現(xiàn)有其他線程來(lái)競(jìng)爭(zhēng)鎖,就會(huì)收回之前分配好的偏好。 4.輕量級(jí)鎖 如果偏向鎖沒(méi)能實(shí)現(xiàn),也就是說(shuō)有多個(gè)線程競(jìng)爭(zhēng)鎖,那么就會(huì)采用輕量級(jí)鎖。 其實(shí)就是將對(duì)象里的輕量級(jí)鎖指針指向一個(gè)已經(jīng)獲取了鎖的線程,然后判斷一下是不是自己加的鎖,如果是就直接執(zhí)行,如果不是說(shuō)明有其他線程加了鎖,就會(huì)升級(jí)為重量級(jí)鎖,重量級(jí)鎖流程我們上文中介紹原子性的時(shí)候已經(jīng)說(shuō)過(guò)了。 5.適應(yīng)性自旋鎖 在許多場(chǎng)景中,同步資源的鎖定時(shí)間很短,為了這一小段時(shí)間去切換線程,線程掛起和恢復(fù)的花費(fèi)可能會(huì)讓系統(tǒng)得不償失。為了讓當(dāng)前線程“稍等一下”,我們需讓當(dāng)前線程進(jìn)行自旋。 如果在自旋完成后前面鎖同步資源的線程已經(jīng)釋放了鎖,那么當(dāng)前線程就可以不必阻塞而是直接獲取同步資源,從而避免了切換線程的開(kāi)銷。這就是自旋鎖。 適應(yīng)性自旋鎖意味著自旋的時(shí)間(次數(shù))不再固定,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來(lái)決定。 總結(jié) 到這里,有關(guān)synchronized的底層實(shí)現(xiàn)我們基本上已經(jīng)聊完了。 使用鎖來(lái)保證原子性,使用內(nèi)存屏障來(lái)保證可見(jiàn)性和有序性。 同時(shí)jvm又對(duì)sychronized做了一些優(yōu)化。 相信小伙伴們理解了本文的內(nèi)容,會(huì)收獲頗豐。 那我們下次再見(jiàn)。 |
|
來(lái)自: HUC王子 > 《JAVA并發(fā)》