淺談 synchronized 鎖機(jī)制原理 與 Lock 鎖機(jī)制
閱讀本文大概需要 10 分鐘。
來(lái)自:blog.csdn.net/a745233700/article/details/119923661
前言

一、synchronized鎖機(jī)制
1、synchronized 的作用:
修飾實(shí)例方法:作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實(shí)例的鎖 修飾靜態(tài)方法:作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要獲得當(dāng)前類對(duì)象的鎖 修飾代碼塊:指定加鎖對(duì)象,進(jìn)入同步代碼庫(kù)前要獲得給定對(duì)象的鎖
2、synchronized 底層語(yǔ)義原理:
monitor 實(shí)現(xiàn)的(無(wú)論是顯示同步還是隱式同步都是如此),每個(gè)對(duì)象的對(duì)象頭都關(guān)聯(lián)著一個(gè) monitor 對(duì)象,當(dāng)一個(gè) monitor 被某個(gè)線程持有后,它便處于鎖定狀態(tài)。在 HotSpot 虛擬機(jī)中,monitor 是由 ObjectMonitor 實(shí)現(xiàn)的,每個(gè)等待鎖的線程都會(huì)被封裝成 ObjectWaiter 對(duì)象,ObjectMonitor 中有兩個(gè)集合,_WaitSet 和 _EntryList,用來(lái)保存 ObjectWaiter 對(duì)象列表 ,owner 區(qū)域指向持有 ObjectMonitor 對(duì)象的線程。_EntryList 集合嘗試獲取 moniter,當(dāng)線程獲取到對(duì)象的 monitor 后進(jìn)入 _Owner 區(qū)域并把 _owner 變量設(shè)置為當(dāng)前線程,同時(shí) monitor 中的計(jì)數(shù)器 count 加1;若線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的 monitor,count自減1,owner 變量恢復(fù)為 null,同時(shí)該線程進(jìn)入 _WaitSet 集合中等待被喚醒。若當(dāng)前線程執(zhí)行完畢也將釋放 monitor 并復(fù)位變量的值,以便其他線程獲取 monitor。如下圖所示:_EntryList:存儲(chǔ)處于Blocked狀態(tài)的ObjectWaiter對(duì)象列表。_WaitSet:存儲(chǔ)處于wait狀態(tài)的ObjectWaiter對(duì)象列表。

3、 synchronized 的顯式同步與隱式同步:
monitorenter 和 monitorexit 指令,而隱式同步并不是由 monitorenter 和 monitorexit 指令來(lái)實(shí)現(xiàn)同步的,而是由方法調(diào)用指令讀取運(yùn)行時(shí)常量池中方法的 ACC_SYNCHRONIZED 標(biāo)志來(lái)隱式實(shí)現(xiàn)的。3.1、synchronized 代碼塊底層原理:
synchronized 同步語(yǔ)句塊的實(shí)現(xiàn)是顯式同步的,通過(guò) monitorenter 和 monitorexit 指令實(shí)現(xiàn),其中 monitorenter 指令指向同步代碼塊的開(kāi)始位置,monitorexit 指令則指明同步代碼塊的結(jié)束位置,當(dāng)執(zhí)行 monitorenter 指令時(shí),當(dāng)前線程將嘗試獲取 objectref(即對(duì)象鎖)所對(duì)應(yīng)的 monitor 的持有權(quán):當(dāng)對(duì)象鎖的
monitor的進(jìn)入計(jì)數(shù)器為 0,那線程可以成功取得monitor,并將計(jì)數(shù)器值設(shè)置為 1,取鎖成功。如果當(dāng)前線程已經(jīng)擁有對(duì)象鎖的
monitor的持有權(quán),那它可以重入這個(gè)monitor,重入時(shí)計(jì)數(shù)器的值也會(huì)加1。若其他線程已經(jīng)擁有對(duì)象鎖的
monitor的所有權(quán),那當(dāng)前線程將被阻塞,直到正在執(zhí)行線程執(zhí)行完畢,即monitorexit指令被執(zhí)行,執(zhí)行線程將釋放monitor并設(shè)置計(jì)數(shù)器值為0,其他線程將有機(jī)會(huì)持有monitor。
monitorenter 指令都有執(zhí)行其對(duì)應(yīng) monitorexit 指令。為了保證在方法異常完成時(shí),monitorenter 和 monitorexit 指令依然可以正確配對(duì)執(zhí)行,編譯器會(huì)自動(dòng)產(chǎn)生一個(gè)異常處理器,這個(gè)異常處理器可處理所有的異常,它的目的就是用來(lái)執(zhí)行 monitorexit 指令。
3.2、synchronized 方法底層原理:
synchronized 同步方法的實(shí)現(xiàn)是隱式的,無(wú)需通過(guò)字節(jié)碼指令來(lái)控制,它是在方法調(diào)用和返回操作之中實(shí)現(xiàn)。JVM 可以通過(guò)方法常量池中的方法表結(jié)構(gòu)(method_info Structure)中的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志 判斷一個(gè)方法是否同步方法。ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志是否被設(shè)置,如果設(shè)置了,標(biāo)識(shí)該方法是一個(gè)同步方法,執(zhí)行線程將先持有 monitor, 然后再執(zhí)行方法,最后再方法完成(無(wú)論是正常完成還是非正常完成)時(shí)釋放 monitor。在方法執(zhí)行期間,執(zhí)行線程持有了 monitor,其他任何線程都無(wú)法再獲得同一個(gè) monitor。monitor 將在異常拋到同步方法之外時(shí)自動(dòng)釋放。
4、JVM 對(duì) synchronized 鎖的優(yōu)化:
monitor 是依賴于操作系統(tǒng)的 Mutex 互斥量來(lái)實(shí)現(xiàn)的,操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高。在 JDK6 之后,synchronized 在 JVM 層面做了優(yōu)化,減少鎖的獲取和釋放所帶來(lái)的性能消耗,主要優(yōu)化方向有以下幾點(diǎn):4.1、鎖升級(jí):偏向鎖->輕量級(jí)鎖->自旋鎖->重量級(jí)鎖
CAS 并配合 Mark Word 一起實(shí)現(xiàn)的。對(duì)象頭 Mark Word 指向類的指針 數(shù)組長(zhǎng)度 實(shí)例數(shù)據(jù) 對(duì)齊填充
hashcode、分代年齡、鎖標(biāo)記位相關(guān)的信息,由于對(duì)象頭的信息是與對(duì)象自身定義的數(shù)據(jù)沒(méi)有關(guān)系的額外存儲(chǔ)成本,因此考慮到 JVM 的空間效率,Mark Word 被設(shè)計(jì)成為一個(gè)非固定的數(shù)據(jù)結(jié)構(gòu),以便存儲(chǔ)更多有效的數(shù)據(jù),它會(huì)根據(jù)對(duì)象本身的狀態(tài)復(fù)用自己的存儲(chǔ)空間,在 32位 JVM 中的長(zhǎng)度是 32 位,具體信息如下圖所示:
4.1.2、鎖升級(jí)過(guò)程:
如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,并且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間,比如100個(gè)循環(huán)。
相反的,如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò),那在以后要獲取這個(gè)鎖時(shí)將可能減少自旋時(shí)間甚至省略自旋過(guò)程,以避免浪費(fèi)處理器資源。
Monitor 中并阻塞在 _EntryList 集合中(具體的爭(zhēng)奪鎖的原理見(jiàn)該部分的第2點(diǎn):synchronized 底層語(yǔ)義原理)。
4.2、鎖消除:
4.3、鎖粗化:
5、偏向鎖的廢除:
為了支持偏向鎖使得代碼復(fù)雜度大幅度提升,并且對(duì) HotSpot 的其他組件產(chǎn)生了影響,這種復(fù)雜性已成為理解代碼的障礙,也阻礙了對(duì)同步系統(tǒng)進(jìn)行重構(gòu)
在更高的 JDK 版本中針對(duì)多線程場(chǎng)景推出了性能更高的并發(fā)數(shù)據(jù)結(jié)構(gòu),所以過(guò)去看到的性能提升,在現(xiàn)在看來(lái)已經(jīng)不那么明顯了。
圍繞線程池隊(duì)列和工作線程構(gòu)建的應(yīng)用程序,性能通常在禁用偏向鎖的情況下變得更好。
二、Lock 鎖機(jī)制
1、Lock 鎖是什么:
lock() 方法與 unlock() 對(duì)臨界區(qū)進(jìn)行加鎖與釋放鎖,當(dāng)前線程獲取到鎖之后,其他線程由于無(wú)法持有鎖將無(wú)法進(jìn)入臨界區(qū),直到當(dāng)前線程釋放鎖,unlock() 操作必須在 finally 代碼塊中,這樣可以確保即使臨界區(qū)執(zhí)行拋出異常,線程最終也能正常釋放鎖。2、ReentrantLock 重入鎖:
ReentrantLock 重入鎖是基于 AQS 框架并實(shí)現(xiàn)了 Lock 接口,支持一個(gè)線程對(duì)資源重復(fù)加鎖,作用與 synchronized 鎖機(jī)制相當(dāng),但比 synchronized 更加靈活,同時(shí)也支持公平鎖和非公平鎖。2.1、ReentrantLock 與 synchronized 的區(qū)別:
ReentrantLock 依賴于 API,是顯式鎖,需要 lock() 和 unlock() 方法配合 try/finally 語(yǔ)句塊來(lái)完成。ReentrantLock 在發(fā)生異常時(shí),如果沒(méi)有主動(dòng)通過(guò) unLock() 去釋放鎖,則很可能造成死鎖現(xiàn)象,這也是 unLock() 語(yǔ)句必須寫(xiě)在 finally 語(yǔ)句塊的原因。ReentrantLock 相比于 synchronzied 更加靈活, 除了擁有 synchronzied 的所有功能外,還提供了其他特性:ReentrantLock可以實(shí)現(xiàn)公平鎖,而synchronized不能保證公平性。ReentrantLock可以知道有沒(méi)有成功獲取鎖(tryLock),而synchronized不支持該功能ReentrantLock可以讓等待鎖的線程響應(yīng)中斷,而使用synchronized時(shí),等待的線程不能夠響應(yīng)中斷,會(huì)一直等待下去;ReentrantLock可以基于Condition實(shí)現(xiàn)多條件的等待喚醒機(jī)制,而如果使用synchronized,則只能有一個(gè)等待隊(duì)列
ReentrantLock 的性能要遠(yuǎn)遠(yuǎn)優(yōu)于 synchronizsed。但是在 JDK6 及以后的版本,JVM 對(duì) synchronized 進(jìn)行了優(yōu)化,所以兩者的性能變得差不多了synchronizsed 和 ReentrantLock 都是可重入鎖,在使用選擇上需要根據(jù)具體場(chǎng)景而定,大部分情況下依然建議使用 synchronized 關(guān)鍵字,原因之一是使用方便語(yǔ)義清晰,二是性能上虛擬機(jī)已為我們自動(dòng)優(yōu)化。如果確實(shí)需要使用到 ReentrantLock 提供的多樣化特性時(shí),我們可以選擇ReentrantLock3、ReadWriteLock 讀寫(xiě)鎖:
ReentrantLock 某些時(shí)候有局限,如果使用 ReentrantLock,主要是為了防止線程A在寫(xiě)數(shù)據(jù)、線程B在讀數(shù)據(jù)造成的數(shù)據(jù)不一致,但如果線程C在讀數(shù)據(jù)、線程D也在讀數(shù)據(jù),由于讀數(shù)據(jù)是不會(huì)改變數(shù)據(jù)內(nèi)容的,所以就沒(méi)有必要加鎖,但如果使用了 ReentrantLock,那么還是加鎖了,反而降低了程序的性能,因此誕生了讀寫(xiě)鎖 ReadWriteLock。ReadWriteLock 是一個(gè)接口,而 ReentrantReadWriteLock 是 ReadWriteLock 接口的具體實(shí)現(xiàn),實(shí)現(xiàn)了讀寫(xiě)的分離,讀鎖是共享的,寫(xiě)鎖是獨(dú)占的,讀和讀之間不會(huì)互斥,讀和寫(xiě)、寫(xiě)和寫(xiě)之間才會(huì)互斥,提升了讀寫(xiě)的性能。推薦閱讀:
所有程序員都應(yīng)該知道的 6 個(gè)軟件開(kāi)發(fā)步驟
無(wú)網(wǎng)絡(luò)環(huán)境,如何部署Docker鏡像?
互聯(lián)網(wǎng)初中高級(jí)大廠面試題(9個(gè)G) 內(nèi)容包含Java基礎(chǔ)、JavaWeb、MySQL性能優(yōu)化、JVM、鎖、百萬(wàn)并發(fā)、消息隊(duì)列、高性能緩存、反射、Spring全家桶原理、微服務(wù)、Zookeeper......等技術(shù)棧!
?戳閱讀原文領(lǐng)取! 朕已閱


