1、synchronized 的實(shí)現(xiàn)原理以及鎖優(yōu)化?

synchronized

1:鎖作用在不同的位置,鎖的對(duì)象不同

? ? ? ?a) 對(duì)于同步方法,鎖是當(dāng)前實(shí)例對(duì)象

? ? ? ? b) 對(duì)于靜態(tài)同步方法,鎖是當(dāng)前對(duì)象的Class對(duì)象。

? ? ? ? c) 對(duì)于同步方法塊,鎖是synchonized括號(hào)里配置的對(duì)象。

如下圖:

解析成字節(jié)碼指令:



結(jié)論:同步方法和靜態(tài)同步方法:依靠的是方法修飾符上的ACC_SYNCHRONIZED實(shí)現(xiàn)

????????????????a)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問標(biāo)志是否被設(shè)置;

????????????????b)如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor;

????????????????c)在方法執(zhí)行期間,其他任何線程都無法再獲得同一個(gè)monitor對(duì)象。

? ? ? ? ? ?同步代碼塊:使用monitorenter和monitorexit指令實(shí)現(xiàn)的,

? ? ? ? ? ? ? ?a)? 會(huì)在同步塊的區(qū)域通過監(jiān)聽器對(duì)象去獲取鎖和釋放鎖,從而在字節(jié)碼層面來控制同步scope.?

? ? ? ? ? ? ? ? b)??monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結(jié)束處和異常處(類似

? ? ? ? ? ? ? ? ? ? ? try...finally...), JVM要保證每個(gè) monitorenter必須有對(duì)應(yīng)的monitorexit與之配對(duì)。

? ? ? ? ? ? ? ?c ) 任何對(duì)象都有一個(gè) monitor 與之關(guān)聯(lián),當(dāng)且一個(gè)monitor 被持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指 令時(shí),將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的 monitor 的所有權(quán),即嘗試獲得對(duì)象的鎖。


2:同步原理

2.1:鎖的信息存儲(chǔ)在對(duì)象頭中,對(duì)象頭主要分為兩個(gè)部分:

1:"Klass Pointer":對(duì)象指向它的類的元數(shù)據(jù)的指針,虛擬機(jī)通過這個(gè)指針來確定這個(gè)對(duì)象是哪個(gè)類的實(shí)例

(數(shù)組,對(duì)象頭中還須有一塊用于記錄數(shù)組長度的數(shù)據(jù),因?yàn)樘摂M機(jī)可以通過普通Java對(duì)象的元數(shù)據(jù)信息確定Java對(duì)象的大小,但是從數(shù)組的元數(shù)據(jù)中無法確定數(shù)組的大小。)

2:"Mark Word":

? ? ? ?a)用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù), 如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標(biāo)志、線程持有的鎖、偏 向線程ID、偏向時(shí)間戳等等。

? ? ? ? b)64位JVM下Mark Word大小的64位的,32位JVM下Mark Word大小的32位的。

? ? ? ? c)在運(yùn)行期間隨著鎖標(biāo)志位的變化存儲(chǔ)的數(shù)據(jù)也會(huì)變化,具體有如下幾種運(yùn)行期間(32位為例),鎖標(biāo)志位?和?是否偏向鎖?確定唯一的鎖狀態(tài):

2.2:Monitor

????????Monitor是 synchronized?重量級(jí)鎖的實(shí)現(xiàn)關(guān)鍵。鎖的標(biāo)識(shí)位為?10?

????????Monitor是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個(gè)對(duì)象都有一個(gè)monitor與之關(guān)聯(lián)。每一個(gè)線程都有一個(gè)可用monitor record列表(當(dāng)前線? ?程對(duì)象monitor),同時(shí)還有一個(gè)全局可用列表(全局對(duì)象monitor)。每一個(gè)被鎖住的對(duì)象,都會(huì)和一個(gè)monitor關(guān)聯(lián)。

????????當(dāng)一個(gè)monitor被某個(gè)線程持有后,它便處于鎖定狀態(tài)。此時(shí),對(duì)象頭中 MarkWord的指向互斥量的指針,就是指向鎖對(duì)象的monitor起始地址。

monitor是由 ObjectMonitor 實(shí)現(xiàn)的,其主要數(shù)據(jù)結(jié)構(gòu)如下:



》》EntryList?和?_WaitSet :用來保存ObjectWaiter對(duì)象列表(每個(gè)等待鎖的線程都會(huì)被封裝成ObjectWaiter對(duì)象)

》》_owner :指向持有 objectMonitor的線程。

根據(jù)虛擬機(jī)規(guī)范的要求,在執(zhí)行monitorenter指令時(shí),會(huì)嘗試獲取對(duì)象的鎖。如果對(duì)象沒有被鎖定(獲取鎖),獲取對(duì)象已經(jīng)被該線程鎖定(鎖重入)。則把計(jì)數(shù)器加1(_count 加1)。相應(yīng)的,在執(zhí)行monitorexit指令時(shí),會(huì)講計(jì)數(shù)器減1。當(dāng)計(jì)數(shù)器為0時(shí),_owner指向Null,鎖就被釋放。(摘自《深入理解JAVA虛擬機(jī)》)?


當(dāng)多個(gè)線程同時(shí)訪問一個(gè)同步代碼時(shí),首先會(huì)進(jìn)入?_EntryList?集合,當(dāng)線程獲取到對(duì)象的monitor后,會(huì)進(jìn)入_owner 區(qū)域,然后把monitor中的?_owner?變量修改為當(dāng)前線程,同時(shí)monitor中的計(jì)數(shù)器_count?會(huì)加1。

如果線程調(diào)用wait()方法,將釋放當(dāng)前持有的monitor,_owner變量恢復(fù)為null,_count變量減1,同時(shí)該線程進(jìn)入_WaitSet等待被喚醒。(wait()和sleep()的區(qū)別,wait是Object的,并且wait會(huì)釋放鎖。)

由此看來 monitor對(duì)象存在于每個(gè)Java對(duì)象的對(duì)象頭中,synchronized鎖便是通過這種方式獲取鎖的。

這就解釋了為什么Java所有對(duì)象都可以作為鎖,同時(shí)也解釋了 wait()? notify()? notifyAll() 為什么存在于頂級(jí)對(duì)象Object中。


3:?JVM中鎖的優(yōu)化

3.1:鎖機(jī)制升級(jí)流程

偏向鎖--》輕量級(jí)鎖--》重量級(jí)鎖

3.2 偏向鎖

原因:

大多數(shù)情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。

過程:

1)當(dāng)一個(gè)線程訪問同步塊并獲取鎖時(shí),簡單地測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖

2)如果成功,表示線程已經(jīng)獲得了鎖。

3)如果測(cè)試失敗,判斷目前鎖的狀態(tài)是否是偏向鎖(Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1)

4)若當(dāng)前是偏向鎖,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程

5)若不是偏向鎖,則使用CAS競爭鎖。

注意:當(dāng)鎖有競爭關(guān)系的時(shí)候,需要解除偏向鎖,進(jìn)入輕量級(jí)鎖。

涉及參數(shù):

偏向鎖在Java 6和Java 7里是默認(rèn)啟用的,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,

如有必要可以使用JVM參數(shù)來關(guān)閉延遲:XX:BiasedLockingStartupDelay=0。

如果確定應(yīng)用的鎖通常情況下處于競爭狀態(tài),可以通過JVM參數(shù)關(guān)閉偏向鎖:-XX:-UseBiasedLocking=false,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。

3.3?輕量級(jí)鎖

1>加鎖

線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。

然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當(dāng)前線程便嘗試使用自旋來獲取鎖。

2>解鎖

輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作將Displaced Mark Word替換回到對(duì)象頭,如果成功,則表示沒有競爭發(fā)生。如果失敗,表示當(dāng)前鎖存在競爭,鎖就會(huì)膨脹成重量級(jí)鎖。

下圖展示兩個(gè)線程同時(shí)爭奪鎖,導(dǎo)致鎖膨脹的流程圖:



自旋的線程在自旋過程中,成功獲得資源(即之前獲的資源的線程執(zhí)行完成并釋放了共享資源),則整個(gè)狀態(tài)依然處于輕量級(jí)鎖的狀態(tài),如果自旋失敗進(jìn)入重量級(jí)鎖的狀態(tài),這個(gè)時(shí)候,自旋的線程進(jìn)行阻塞,等待之前線程執(zhí)行完成并喚醒自己。因?yàn)樽孕龝?huì)消耗CPU,為了避免無用的自旋(比如獲得鎖的線程被阻塞住了),一旦鎖升級(jí)成重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線程試圖獲取鎖時(shí),都會(huì)被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些線程,被喚醒的線程就會(huì)進(jìn)行新一輪的奪鎖之爭


3.4?鎖的優(yōu)缺點(diǎn)對(duì)比


4:幾個(gè)鎖概念

4.1 鎖粗化

就是將多次連接在一起的加鎖、解鎖操作合并為一次,將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖。舉個(gè)例子:

publicclassLockCoarsening{privateStringBuffer stringBuffer =newStringBuffer(20);publicvoidappend(){? ? ? ? stringBuffer.append("w");? ? ? ? stringBuffer.append("h");? ? ? ? stringBuffer.append("y");? ? }}

這里每次調(diào)用stringBuffer.append方法都需要加鎖和解鎖,如果虛擬機(jī)檢測(cè)到有一系列連串的對(duì)同一個(gè)對(duì)象加鎖和解鎖操作,就會(huì)將其合并成一次范圍更大的加鎖和解鎖操作,即在第一次append方法時(shí)進(jìn)行加鎖,最后一次append方法結(jié)束后進(jìn)行解鎖。

4.2?鎖消除

鎖消除即刪除不必要的加鎖操作。根據(jù)代碼逃逸技術(shù),如果判斷到一段代碼中,堆上的數(shù)據(jù)不會(huì)逃逸出當(dāng)前線程,那么可以認(rèn)為這段代碼是線程安全的,不必要加鎖。逃逸分析和鎖消除分別可以使用參數(shù)-XX:+DoEscapeAnalysis和-XX:+EliminateLocks(鎖消除必須在-server模式下)開啟

4.3?適應(yīng)性自旋

當(dāng)前鎖處于膨脹,會(huì)進(jìn)行自旋。自旋是需要消耗CPU的,如果一直獲取不到鎖的話,那線程一直處在自旋狀態(tài),消耗CPU資源。為了解決這個(gè)問題JDK采用—適應(yīng)性自旋,線程如果自旋成功了,則下次自旋的次數(shù)會(huì)更多,如果自旋失敗了,則自旋的次數(shù)就會(huì)減少。另外自旋雖然會(huì)占用CPU資源,但不會(huì)一直占用CPU資源,每隔一段時(shí)間會(huì)通過os::NakedYield方法放棄CPU資源,或通過park方法掛起;如果其他線程完成鎖的膨脹操作,則退出自旋并返回.







參考文獻(xiàn):

http://www.itdecent.cn/p/1ea87c152413

http://www.cnblogs.com/xdyixia/p/9364247.html

http://www.itdecent.cn/p/73b9a8466b9c

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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