這篇文章將為大家詳細(xì)講解有關(guān)如何升級(jí)JAVA中的synchronized鎖,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
創(chuàng)新互聯(lián)建站主營(yíng)錦江網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,app開發(fā)定制,錦江h(huán)5小程序開發(fā)搭建,錦江網(wǎng)站營(yíng)銷推廣歡迎錦江等地區(qū)企業(yè)咨詢
1、synchronized 的基本認(rèn)識(shí)
場(chǎng)景:Synchronized是一個(gè)同步關(guān)鍵字,在某些多線程場(chǎng)景下,如果不進(jìn)行同步會(huì)導(dǎo)致數(shù)據(jù)不安全,而Synchronized關(guān)鍵字就是用于代碼同步。什么情況下會(huì)數(shù)據(jù)不安全呢,要滿足兩個(gè)條件:一是數(shù)據(jù)共享(臨界資源),二是多線程同時(shí)訪問(wèn)并改變?cè)摂?shù)據(jù)。
在多線程并發(fā)編程中 synchronized 稱呼為重量級(jí)鎖。但是,隨著 Java SE 1.6 對(duì) synchronized 進(jìn)行了各種優(yōu)化之后,有些情況下它就并不那么重,Java SE 1.6 中為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗而引入的偏向鎖和輕量級(jí)鎖。
1.1 synchronized 有三種方式來(lái)加鎖
1.1.1. 修飾實(shí)例方法,作用于當(dāng)前實(shí)例加鎖,進(jìn)入同步代碼前 要獲得當(dāng)前實(shí)例的鎖
1.1.2. 靜態(tài)方法,作用于當(dāng)前類對(duì)象加鎖,進(jìn)入同步代碼前要 獲得當(dāng)前類對(duì)象的鎖
1.1.3. 修飾代碼塊,指定加鎖對(duì)象,對(duì)給定對(duì)象加鎖,進(jìn)入同 步代碼庫(kù)前要獲得給定對(duì)象的鎖。
1.2 Mark word
Mark word 記錄了對(duì)象和鎖有關(guān)的信息,當(dāng)某個(gè)對(duì)象被 synchronized 關(guān)鍵字當(dāng)成同步鎖時(shí),那么圍繞這個(gè)鎖的一 系列操作都和 Mark word 有關(guān)系。Mark Word 在 32 位虛 擬機(jī)的長(zhǎng)度是 32bit、在 64 位虛擬機(jī)的長(zhǎng)度是 64bit。 Mark Word 里面存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化, Mark Word 可能變化為存儲(chǔ)以下 5 中情況
32位
鎖基礎(chǔ)
1.2.1. 首先,Java 中的每個(gè)對(duì)象都派生自 Object 類,而每個(gè)Java Object 在 JVM 內(nèi)部都有一個(gè) native 的 C++對(duì)象 oop/oopDesc 進(jìn)行對(duì)應(yīng)。
1.2.2. 線程在獲取鎖的時(shí)候,實(shí)際上就是獲得一個(gè)監(jiān)視器對(duì)象 (monitor) ,monitor 可以認(rèn)為是一個(gè)同步對(duì)象,所有的 Java 對(duì)象是天生攜帶 monitor。
2、synchronized 鎖的升級(jí)
在分析 markword 時(shí),提到了偏向鎖、輕量級(jí)鎖、重量級(jí) 鎖。
JDK1.6 之后做了一些優(yōu)化,為了減少獲得鎖和釋放鎖帶來(lái) 的性能開銷,引入了偏向鎖、輕量級(jí)鎖的概念。因此大家 會(huì)發(fā)現(xiàn)在 synchronized 中,鎖存在四種狀態(tài) 分別是:無(wú)鎖、偏向鎖、輕量級(jí)鎖、重量級(jí)鎖; 鎖的狀態(tài) 根據(jù)競(jìng)爭(zhēng)激烈的程度從低到高不斷升級(jí)。
2.1 偏向鎖的基本原理
前面說(shuō)過(guò),大部分情況下,鎖不僅僅不存在多線程競(jìng)爭(zhēng), 而是總是由同一個(gè)線程多次獲得,為了讓線程獲取鎖的代 價(jià)更低就引入了偏向鎖的概念。怎么理解偏向鎖呢? 當(dāng)一個(gè)線程訪問(wèn)加了同步鎖的代碼塊時(shí),會(huì)在對(duì)象頭中存 儲(chǔ)當(dāng)前線程的 ID,后續(xù)這個(gè)線程進(jìn)入和退出這段加了同步 鎖的代碼塊時(shí),不需要再次加鎖和釋放鎖。而是直接比較 對(duì)象頭里面是否存儲(chǔ)了指向當(dāng)前線程的偏向鎖。如果相等 表示偏向鎖是偏向于當(dāng)前線程的,就不需要再嘗試獲得鎖 了
2.1.1 偏向鎖的獲取和撤銷邏輯
1. 首先獲取鎖 對(duì)象的 Markword,判斷是否處于可偏向狀 態(tài)。(biased_lock=1、且 ThreadId 為空)
2. 如果是可偏向狀態(tài),則通過(guò) CAS 操作,把當(dāng)前線程的 ID 寫入到 MarkWord a) 如果 cas 成功,那么 markword 就會(huì)變成這樣。 表示已經(jīng)獲得了鎖對(duì)象的偏向鎖,接著執(zhí)行同步代碼 塊 b) 如果 cas 失敗,說(shuō)明有其他線程已經(jīng)獲得了偏向鎖, 這種情況說(shuō)明當(dāng)前鎖存在競(jìng)爭(zhēng),需要撤銷已獲得偏向 鎖的線程,并且把它持有的鎖升級(jí)為輕量級(jí)鎖(這個(gè) 操作需要等到全局安全點(diǎn),也就是沒(méi)有線程在執(zhí)行字 節(jié)碼)才能執(zhí)行
3. 如果是已偏向狀態(tài),需要檢查 markword 中存儲(chǔ)的 ThreadID 是否等于當(dāng)前線程的 ThreadID a) 如果相等,不需要再次獲得鎖,可直接執(zhí)行同步代碼 塊 b) 如果不相等,說(shuō)明當(dāng)前鎖偏向于其他線程,需要撤銷 偏向鎖并升級(jí)到輕量級(jí)鎖
2.1.2 偏向鎖的撤銷
偏向鎖的撤銷并不是把對(duì)象恢復(fù)到無(wú)鎖可偏向狀態(tài)(因?yàn)?偏向鎖并不存在鎖釋放的概念),而是在獲取偏向鎖的過(guò)程 中,發(fā)現(xiàn) cas 失敗也就是存在線程競(jìng)爭(zhēng)時(shí),直接把被偏向 的鎖對(duì)象升級(jí)到被加了輕量級(jí)鎖的狀態(tài)。
對(duì)原持有偏向鎖的線程進(jìn)行撤銷時(shí),原獲得偏向鎖的線程 有兩種情況:
2.1.2.1. 原獲得偏向鎖的線程如果已經(jīng)退出了臨界區(qū),也就是同 步代碼塊執(zhí)行完了,那么這個(gè)時(shí)候會(huì)把對(duì)象頭設(shè)置成無(wú) 鎖狀態(tài)并且爭(zhēng)搶鎖的線程可以基于 CAS 重新偏向但前 線程
2.1.2.2. 如果原獲得偏向鎖的線程的同步代碼塊還沒(méi)執(zhí)行完,處 于臨界區(qū)之內(nèi),這個(gè)時(shí)候會(huì)把原獲得偏向鎖的線程升級(jí) 為輕量級(jí)鎖后繼續(xù)執(zhí)行同步代碼塊。在我們的應(yīng)用開發(fā)中,絕大部分情況下一定會(huì)存在 2 個(gè)以 上的線程競(jìng)爭(zhēng),那么如果開啟偏向鎖,反而會(huì)提升獲取鎖 的資源消耗。所以可以通過(guò) jvm 參數(shù) UseBiasedLocking 來(lái)設(shè)置開啟或關(guān)閉偏向鎖
2.2 輕量級(jí)鎖的基本原理
輕量級(jí)鎖的加鎖和解鎖邏輯
2.2.1 鎖升級(jí)為輕量級(jí)鎖之后,對(duì)象的 Markword 也會(huì)進(jìn)行相應(yīng) 的的變化。
升級(jí)為輕量級(jí)鎖的過(guò)程:
2.2.1.1. 線程在自己的棧楨中創(chuàng)建鎖記錄 LockRecord。
2.2.1.2. 將鎖對(duì)象的對(duì)象頭中的MarkWord復(fù)制到線程的剛剛創(chuàng) 建的鎖記錄中。
2.2.1.3. 將鎖記錄中的 Owner 指針指向鎖對(duì)象。
2.2.1.4. 將鎖對(duì)象的對(duì)象頭的 MarkWord替換為指向鎖記錄的指 針。
2.2.2 自旋鎖
輕量級(jí)鎖在加鎖過(guò)程中,用到了自旋鎖
所謂自旋,就是指當(dāng)有另外一個(gè)線程來(lái)競(jìng)爭(zhēng)鎖時(shí),這個(gè)線 程會(huì)在原地循環(huán)等待,而不是把該線程給阻塞,直到那個(gè) 獲得鎖的線程釋放鎖之后,這個(gè)線程就可以馬上獲得鎖的。
注意,鎖在原地循環(huán)的時(shí)候,是會(huì)消耗 cpu 的,就相當(dāng)于 在執(zhí)行一個(gè)啥也沒(méi)有的 for 循環(huán)。
所以,輕量級(jí)鎖適用于那些同步代碼塊執(zhí)行的很快的場(chǎng)景,這樣,線程原地等待很短的時(shí)間就能夠獲得鎖了。 自旋鎖的使用,其實(shí)也是有一定的概率背景,在大部分同 步代碼塊執(zhí)行的時(shí)間都是很短的。所以通過(guò)看似無(wú)異議的 循環(huán)反而能提升鎖的性能。 但是自旋必須要有一定的條件控制,否則如果一個(gè)線程執(zhí)行同步代碼塊的時(shí)間很長(zhǎng),那么這個(gè)線程不斷的循環(huán)反而 會(huì)消耗 CPU 資源。默認(rèn)情況下自旋的次數(shù)是 10 次, 可以通過(guò) preBlockSpin 來(lái)修改
在 JDK1.6 之后,引入了自適應(yīng)自旋鎖,自適應(yīng)意味著自旋 的次數(shù)不是固定不變的,而是根據(jù)前一次在同一個(gè)鎖上自 旋的時(shí)間以及鎖的擁有者的狀態(tài)來(lái)決定。 如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過(guò)鎖,并 且持有鎖的線程正在運(yùn)行中,那么虛擬機(jī)就會(huì)認(rèn)為這次自 旋也是很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相 對(duì)更長(zhǎng)的時(shí)間。
如果對(duì)于某個(gè)鎖,自旋很少成功獲得過(guò), 那在以后嘗試獲取這個(gè)鎖時(shí)將可能省略掉自旋過(guò)程,直接 阻塞線程,避免浪費(fèi)處理器資源
2.2.3 輕量級(jí)鎖的解鎖
輕量級(jí)鎖的鎖釋放邏輯其實(shí)就是獲得鎖的逆向邏輯,通過(guò) CAS 操作把線程棧幀中的 LockRecord 替換回到鎖對(duì)象的 MarkWord 中,如果成功表示沒(méi)有競(jìng)爭(zhēng)。如果失敗,表示 當(dāng)前鎖存在競(jìng)爭(zhēng),那么輕量級(jí)鎖就會(huì)膨脹成為重量級(jí)鎖
加了同步代碼塊以后,在字節(jié)碼中會(huì)看到一個(gè) monitorenter 和 monitorexit。
每一個(gè) JAVA 對(duì)象都會(huì)與一個(gè)監(jiān)視器 monitor 關(guān)聯(lián),我們 可以把它理解成為一把鎖,當(dāng)一個(gè)線程想要執(zhí)行一段被 synchronized 修飾的同步方法或者代碼塊時(shí),該線程得先 獲取到 synchronized 修飾的對(duì)象對(duì)應(yīng)的 monitor。
2.3 -重量級(jí)鎖的基本原理
當(dāng)輕量級(jí)鎖膨脹到重量級(jí)鎖之后,意味著線程只能被掛起 阻塞來(lái)等待被喚醒了。
monitorenter 表示去獲得一個(gè)對(duì)象監(jiān)視器。monitorexit 表 示釋放 monitor 監(jiān)視器的所有權(quán),使得其他被阻塞的線程 可以嘗試去獲得這個(gè)監(jiān)視器 monitor 依賴操作系統(tǒng)的 MutexLock(互斥鎖)來(lái)實(shí)現(xiàn)的, 線 程被阻塞后便進(jìn)入內(nèi)核(Linux)調(diào)度狀態(tài),這個(gè)會(huì)導(dǎo)致系 統(tǒng)在用戶態(tài)與內(nèi)核態(tài)之間來(lái)回切換,嚴(yán)重影響鎖的性能
關(guān)于如何升級(jí)JAVA中的synchronized鎖就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺(jué)得文章不錯(cuò),可以把它分享出去讓更多的人看到。