Synchronized 和后來(lái)出的這個(gè)lock鎖的區(qū)別
在并發(fā)編程中,多個(gè)線(xiàn)程訪(fǎng)問(wèn)同一個(gè)共享資源時(shí),我們必須考慮如何維護(hù)數(shù)據(jù)的原子性。在
JDK1.5 之前,Java 是依靠 Synchronized 關(guān)鍵字實(shí)現(xiàn)鎖功能來(lái)做到這點(diǎn)的。Synchronized 是 JVM 實(shí)現(xiàn)的一種內(nèi)置鎖,鎖的獲取和釋放是由 JVM 隱式實(shí)現(xiàn)。
到了 JDK1.5 版本,并發(fā)包中新增了 Lock 接口來(lái)實(shí)現(xiàn)鎖功能,它提供了與 Synchronized
關(guān)鍵字類(lèi)似的同步功能,只是在使用時(shí)需要顯示獲取和釋放鎖
Lock 同步鎖是基于 Java 實(shí)現(xiàn)的,而 Synchronized 是基于底層操作系統(tǒng)的 Mutex Lock
實(shí)現(xiàn)的,每次獲取和釋放鎖操作都會(huì)帶來(lái)用戶(hù)態(tài)和內(nèi)核態(tài)的切換,從而增加系統(tǒng)性能開(kāi)銷(xiāo)
因此,在鎖競(jìng)爭(zhēng)激烈的情況下,Synchronized 同步鎖在性能上就表現(xiàn)得非常糟糕,它也常
被大家稱(chēng)為重量級(jí)鎖。
1.6以后呢對(duì)這個(gè)Synchronized 鎖進(jìn)行了升級(jí),引入了鎖升級(jí),某些程度上來(lái)說(shuō)呢。再某些業(yè)務(wù)場(chǎng)景已經(jīng)超過(guò)了lock。
這里再次生明Synchronized 是關(guān)鍵字,而lock是通過(guò)是西安這個(gè)lock接口來(lái)實(shí)現(xiàn)這個(gè)所功能的。
Synchronized 底層原理 ,也就是他的同步原理
通常 Synchronized 實(shí)現(xiàn)同步鎖的方式有兩種,一種是修飾方法,一種是修飾方法塊。以
下就是通過(guò) Synchronized 實(shí)現(xiàn)的兩種同步方法加鎖的方式:
?javac -encoding UTF-8 SyncTest.java // 先運(yùn)行編譯 class 文件命令
javap -v SyncTest.class // 再通過(guò) javap 打印出字節(jié)文件
通過(guò)以上命令去反編譯出這個(gè)文件的字節(jié)碼文件可以看到
你會(huì)發(fā)現(xiàn):Synchronized 在修飾同步代碼塊時(shí),是由 monitorenter和 monitorexit 指令來(lái)實(shí)現(xiàn)同步的。進(jìn)入 monitorenter 指令后,線(xiàn)程將持有 Monitor 對(duì)象,退出 monitorenter 指令后,線(xiàn)程將釋放該 Monitor 對(duì)象。注意修飾的代碼塊
而同步方法的字節(jié)碼中,你會(huì)發(fā)現(xiàn):當(dāng) Synchronized 修飾同步方法時(shí),并沒(méi)有發(fā)
現(xiàn) monitorenter 和 monitorexit 指令,而是出現(xiàn)了一個(gè) ACC_SYNCHRONIZED 標(biāo)志。
這是因?yàn)?JVM 使用了 ACC_SYNCHRONIZED 訪(fǎng)問(wèn)標(biāo)志來(lái)區(qū)分一個(gè)方法是否是同步方法
當(dāng)方法調(diào)用時(shí),調(diào)用指令將會(huì)檢查該方法是否被設(shè)置 ACC_SYNCHRONIZED 訪(fǎng)問(wèn)標(biāo)志。
如果設(shè)置了該標(biāo)志,執(zhí)行線(xiàn)程將先持有 Monitor 對(duì)象,然后再執(zhí)行方法。在該方法運(yùn)行期
間,其它線(xiàn)程將無(wú)法獲取到該 Mointor 對(duì)象,當(dāng)方法執(zhí)行完成后,再釋放該 Monitor 對(duì)
象。
?再來(lái)看看 Synchronized 修飾方法是怎么實(shí)現(xiàn)鎖原理的。
JVM 中的同步是基于進(jìn)入和退出管程(Monitor)對(duì)象實(shí)現(xiàn)的。每個(gè)對(duì)象實(shí)例都會(huì)有一個(gè)
Monitor,Monitor 可以和對(duì)象一起創(chuàng)建、銷(xiāo)毀。
當(dāng)多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)一段同步代碼時(shí),多個(gè)線(xiàn)程會(huì)先被存放在 EntryList 集合中,處于
block 狀態(tài)的線(xiàn)程,都會(huì)被加入到該列表。接下來(lái)當(dāng)線(xiàn)程獲取到對(duì)象的 Monitor 時(shí),
Monitor 是依靠底層操作系統(tǒng)的 Mutex Lock 來(lái)實(shí)現(xiàn)互斥的,線(xiàn)程申請(qǐng) Mutex 成功,則持
有該 Mutex,其它線(xiàn)程將無(wú)法獲取到該 Mutex。
注意閱讀下圖
?如果線(xiàn)程調(diào)用 wait() 方法,就會(huì)釋放當(dāng)前持有的 Mutex,并且該線(xiàn)程會(huì)進(jìn)入 WaitSet 集合
中,等待下一次被喚醒。如果當(dāng)前線(xiàn)程順利執(zhí)行完方法,也將釋放 Mutex。
這里插播一下 wait和sleep都釋放鎖碼?好像寫(xiě)代碼的時(shí)候遇到過(guò)
?因 Monitor 是依賴(lài)于底層的操作系統(tǒng)實(shí)現(xiàn),存在用戶(hù)態(tài)與內(nèi)核態(tài)之間的切換,所以增加了性能開(kāi)銷(xiāo)。
鎖升級(jí)優(yōu)化
為了提升性能,JDK1.6 引入了偏向鎖、輕量級(jí)鎖、重量級(jí)鎖概念,來(lái)減少鎖競(jìng)爭(zhēng)帶來(lái)的上
下文切換,而正是新增的 Java 對(duì)象頭實(shí)現(xiàn)了鎖升級(jí)功能。
當(dāng) Java 對(duì)象被 Synchronized 關(guān)鍵字修飾成為同步鎖后,圍繞這個(gè)鎖的一系列升級(jí)操作都
將和 Java 對(duì)象頭有關(guān)。
Java 對(duì)象頭
在 JDK1.6 JVM 中,對(duì)象實(shí)例在堆內(nèi)存中被分為了三個(gè)部分:對(duì)象頭、實(shí)例數(shù)據(jù)和對(duì)齊填
充。其中 Java 對(duì)象頭由 Mark Word、指向類(lèi)的指針以及數(shù)組長(zhǎng)度三部分組成
Mark Word 記錄了對(duì)象和鎖有關(guān)的信息。Mark Word 在 64 位 JVM 中的長(zhǎng)度是 64bit
?鎖升級(jí)功能主要依賴(lài)于 Mark Word 中的鎖標(biāo)志位和釋放偏向鎖標(biāo)志位,Synchronized 同
步鎖就是從偏向鎖開(kāi)始的,隨著競(jìng)爭(zhēng)越來(lái)越激烈,偏向鎖升級(jí)到輕量級(jí)鎖,最終升級(jí)到重量
級(jí)鎖。
1. 偏向鎖
偏向鎖主要用來(lái)優(yōu)化同一線(xiàn)程多次申請(qǐng)同一個(gè)鎖的競(jìng)爭(zhēng)。在某些情況下,大部分時(shí)間是同一
個(gè)線(xiàn)程競(jìng)爭(zhēng)鎖資源,例如,在創(chuàng)建一個(gè)線(xiàn)程并在線(xiàn)程中執(zhí)行循環(huán)監(jiān)聽(tīng)的場(chǎng)景下,或單線(xiàn)程操
作一個(gè)線(xiàn)程安全集合時(shí),同一線(xiàn)程每次都需要獲取和釋放鎖,每次操作都會(huì)發(fā)生用戶(hù)態(tài)與內(nèi)
核態(tài)的切換。
?再自己的同步代碼塊里加鎖,同步代碼塊有全局變量,我們枷鎖,讓它不被別的線(xiàn)程修改
偏向鎖的作用就是,當(dāng)一個(gè)線(xiàn)程再次訪(fǎng)問(wèn)這個(gè)同步代碼或方法時(shí),該線(xiàn)程只需去對(duì)象頭的
Mark Word 中去判斷一下是否有偏向鎖指向它的 ID,無(wú)需再進(jìn)入 Monitor 去競(jìng)爭(zhēng)對(duì)象
了。
當(dāng)對(duì)象被當(dāng)做同步鎖并有一個(gè)線(xiàn)程搶到了鎖時(shí),鎖標(biāo)志位還是 01,“是否偏向鎖”標(biāo)
志位設(shè)置為 1,并且記錄搶到鎖的線(xiàn)程 ID,表示進(jìn)入偏向鎖狀態(tài)。
一旦出現(xiàn)其它線(xiàn)程競(jìng)爭(zhēng)鎖資源時(shí),偏向鎖就會(huì)被撤銷(xiāo)。偏向鎖的撤銷(xiāo)需要等待全局安全點(diǎn),
暫停持有該鎖的線(xiàn)程,同時(shí)檢查該線(xiàn)程是否還在執(zhí)行該方法,如果是,則升級(jí)鎖,反之則被
其它線(xiàn)程搶占。
下圖中紅線(xiàn)流程部分為偏向鎖獲取和撤銷(xiāo)流程:
偏向鎖這個(gè)設(shè)計(jì)到一個(gè)調(diào)優(yōu) 高并發(fā)
在高并發(fā)場(chǎng)景下,當(dāng)大量線(xiàn)程同時(shí)競(jìng)爭(zhēng)同一個(gè)鎖資源時(shí),偏向鎖就會(huì)被撤銷(xiāo),發(fā)生
stop the world 后, 開(kāi)啟偏向鎖無(wú)疑會(huì)帶來(lái)更大的性能開(kāi)銷(xiāo),這時(shí)我們可以通過(guò)添加 JVM
參數(shù)關(guān)閉偏向鎖來(lái)調(diào)優(yōu)系統(tǒng)性能
如果你確定應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài),可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖:-XX:-
UseBiasedLocking=false,那么程序默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)。
2.輕量級(jí)鎖
(1)輕量級(jí)鎖加鎖
????????線(xiàn)程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線(xiàn)程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并
將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱(chēng)為Displaced Mark Word。然后線(xiàn)程嘗試使用
CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線(xiàn)程獲得鎖,如果失
敗,表示其他線(xiàn)程競(jìng)爭(zhēng)鎖,當(dāng)前線(xiàn)程便嘗試使用自旋來(lái)獲取鎖。
(2)輕量級(jí)鎖解鎖
輕量級(jí)解鎖時(shí),會(huì)使用原子的CAS操作將Displaced Mark Word替換回到對(duì)象頭,如果成
功,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生。如果失敗,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖。圖2-2是
兩個(gè)線(xiàn)程同時(shí)爭(zhēng)奪鎖,導(dǎo)致鎖膨脹的流程圖。
?因?yàn)樽孕龝?huì)消耗CPU,為了避免無(wú)用的自旋(比如獲得鎖的線(xiàn)程被阻塞住了),一旦鎖升級(jí)
成重量級(jí)鎖,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。當(dāng)鎖處于這個(gè)狀態(tài)下,其他線(xiàn)程試圖獲取鎖時(shí),
都會(huì)被阻塞住,當(dāng)持有鎖的線(xiàn)程釋放鎖之后會(huì)喚醒這些線(xiàn)程,被喚醒的線(xiàn)程就會(huì)進(jìn)行新一輪
的奪鎖之爭(zhēng)。
這里非常重要的一點(diǎn)就是文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-631494.html
當(dāng)一個(gè)線(xiàn)程訪(fǎng)問(wèn)同步塊并獲取鎖時(shí),會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線(xiàn)程ID文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-631494.html
到了這里,關(guān)于Synchronized同步鎖的優(yōu)化方法 待完工的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!