Monitor實現(xiàn)的鎖屬于重量級鎖,你了解過鎖升級嗎?
前面我們說了 synchronized 底層由monitor實現(xiàn)的,它那 synchronized 到底鎖的是什么呢?隨著 JDK 版本的升級,synchronized 又做出了哪些改變呢?“synchronized 性能很差”的謠言真的存在嗎?
在介紹以上內(nèi)容之前,我們要先知道重量級鎖概念。
當(dāng)另外一個線程執(zhí)行到同步塊的時候,由于它沒有對應(yīng) monitor 的所有權(quán),就會被阻塞,此時控制權(quán)只能交給操作系統(tǒng),也就會從 user mode 切換到 kernel mode, 由操作系統(tǒng)來負(fù)責(zé)線程間的調(diào)度和線程的狀態(tài)變更, 這就需要頻繁的在這兩個模式下切換(上下文轉(zhuǎn)換)。有點競爭就找內(nèi)核的行為很不好,會引起很大的開銷,所以大家都叫它重量級鎖,自然效率也很低,這也就給很多小伙伴留下了一個印象 —— synchronized 關(guān)鍵字相比于其他同步機制性能不好,但其實不然。
在JVM虛擬機中,對象在內(nèi)存中存儲的布局可分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充。
圖片
我們需要重點分析MarkWord對象頭,因為Markword 是保存鎖狀態(tài)的關(guān)鍵,對象鎖狀態(tài)可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖,加上初始的無鎖狀態(tài),可以理解為有 4 種狀態(tài)。想在一個對象中表示這么多信息自然就要用位來存儲。
圖片
thread:持有偏向鎖的線程ID,占23位
我們可以通過lock的標(biāo)識,來判斷是哪一種鎖的等級
在很多的情況下,在Java程序運行時,同步塊中的代碼都是不存在競爭的,不同的線程交替的執(zhí)行同步塊中的代碼。這種情況下,用重量級鎖是沒必要的。因此JVM引入了輕量級鎖的概念。
如果 CPU 通過 CAS(后面會細(xì)講,戳鏈接直達(dá))就能處理好加鎖/釋放鎖,這樣就不會有上下文的切換。
但是當(dāng)競爭很激烈,CAS 嘗試再多也是浪費 CPU,權(quán)衡一下,不如升級成重量級鎖,阻塞線程排隊競爭,也就有了輕量級鎖升級成重量級鎖的過程。
圖片
作為程序員的我們最喜歡用代碼說話,貼心的 openjdk 官網(wǎng)提供了可以查看對象內(nèi)存布局的工具 JOL (java object layout),我們直接通過 Maven 引入到項目中。
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.14</version> </dependency>
public class SyncSample { private static Object LOCK = new Object(); public static void main(String[] args) { System.out.println("----------未進(jìn)入同步塊,MarkWord 為:----------"); System.out.println(ClassLayout.parseInstance(LOCK).toPrintable()); synchronized (LOCK) { System.out.println("----------進(jìn)入同步塊,MarkWord 為:----------"); System.out.println(ClassLayout.parseInstance(LOCK).toPrintable()); } }}
圖片
1.在線程棧中創(chuàng)建一個Lock Record,將其obj字段指向鎖對象。
圖片
2.通過CAS指令將Lock Record的地址存儲在對象頭的mark word中(數(shù)據(jù)進(jìn)行交換),如果對象處于無鎖狀態(tài)則修改成功,代表該線程獲得了輕量級鎖。
圖片
3.如果是當(dāng)前線程已經(jīng)持有該鎖了,代表這是一次鎖重入。設(shè)置Lock Record第一部分為null,起到了一個重入計數(shù)器的作用。
圖片
4.如果CAS修改失敗,說明發(fā)生了競爭,需要膨脹為重量級鎖。
1.遍歷線程棧,找到所有obj字段等于當(dāng)前鎖對象的Lock Record。
2.如果Lock Record的Mark Word為null,代表這是一次重入,將obj設(shè)置為null后continue。
圖片
3.如果Lock Record的 Mark Word不為null,則利用CAS指令將對象頭的mark word恢復(fù)成為無鎖狀態(tài)。如果失敗則膨脹為重量級鎖。
圖片
輕量級鎖在沒有競爭時(就自己這個線程),每次重入仍然需要執(zhí)行 CAS 操作。Java 6 中引入了偏向鎖來做進(jìn)一步優(yōu)化:只有第一次使用 CAS 將線程 ID 設(shè)置到對象的 Mark Word 頭,之后發(fā)現(xiàn)這個線程 ID 是自己的就表示沒有競爭,不用重新 CAS。以后只要不發(fā)生競爭,這個對象就歸該線程所有。
圖片
可是多線程環(huán)境,也不可能只有同一個線程一直獲取這個鎖,其他線程也是要干活的,如果出現(xiàn)多個線程競爭的情況,就會有偏向鎖升級的過程。
1.在線程棧中創(chuàng)建一個Lock Record,將其obj字段指向鎖對象。
圖片
2.通過CAS指令將Lock Record的線程id存儲在對象頭的mark word中,同時也設(shè)置偏向鎖的標(biāo)識為101,如果對象處于無鎖狀態(tài)則修改成功,代表該線程獲得了偏向鎖。
圖片
3.如果是當(dāng)前線程已經(jīng)持有該鎖了,代表這是一次鎖重入。設(shè)置Lock Record第一部分為null,起到了一個重入計數(shù)器的作用。與輕量級鎖不同的時,這里不會再次進(jìn)行cas操作,只是判斷對象頭中的線程id是否是自己,因為缺少了cas操作,性能相對輕量級鎖更好一些。
圖片
思考:偏向鎖可以繞過輕量級鎖,直接升級到重量級鎖嗎?
面試官:Monitor實現(xiàn)的鎖屬于重量級鎖,你了解過鎖升級嗎?
Java中的synchronized有無鎖(無鎖就是沒有對資源進(jìn)行鎖定,任何線程都可以嘗試去修改它)、偏向鎖、輕量級鎖、重量級鎖四種形式,偏向鎖、輕量級鎖、重量級鎖分別對應(yīng)了鎖只被一個線程持有、不同線程交替持有鎖、多線程競爭鎖三種情況
鎖別 | 描述 |
重量級鎖 | 底層使用的Monitor實現(xiàn),里面涉及到了用戶態(tài)和內(nèi)核態(tài)的切換、進(jìn)程的上下文切換,成本較高,性能比較低。 |
輕量級鎖 | 線程加鎖的時間是錯開的(也就是沒有競爭),可以使用輕量級鎖來優(yōu)化。輕量級修改了對象頭的鎖標(biāo)志,相對重量級鎖性能提升很多。每次修改都是CAS操作,保證原子性 |
偏向鎖 | 一段很長的時間內(nèi)都只被一個線程使用鎖,可以使用了偏向鎖,在第一次獲得鎖時,會有一個CAS操作,之后該線程再獲取鎖,只需要判斷mark word中是否是自己的線程id即可,而不是開銷相對較大的CAS命令 |
本文鏈接:http://www.tebozhan.com/showinfo-26-77523-0.htmlJava中的鎖升級機制:偏向鎖、輕量級鎖和重量級鎖
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com