- Synchronized實現(xiàn)同步
- 同步普通方法趁窃,鎖當(dāng)前實例對象
- 同步靜態(tài)方法,鎖當(dāng)前類的Class對象
- 同步方法塊,鎖是Synchronized()里配置的對象
- Synchronized在JVM中的實現(xiàn)原理
JVM基于進(jìn)入和退出Monitor對象來實現(xiàn)同步。編譯后插入monitorenter
到同步代碼塊開始的位置,monitorexit
插入到方法結(jié)束處和異常處罐氨。monitor被持有后,進(jìn)入鎖定狀態(tài)滩援,當(dāng)線程執(zhí)行到monitorenter指令時栅隐,將嘗試獲取對象對應(yīng)的monitor所有權(quán),既嘗試獲得對象的鎖玩徊。
synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令租悄,取得代之的確實是 ACC_SYNCHRONIZED 標(biāo)識,該標(biāo)識指明了該方法是一個同步方法恩袱,JVM 通過該 ACC_SYNCHRONIZED 訪問標(biāo)志來辨別一個方法是否聲明為同步方法泣棋,從而執(zhí)行相應(yīng)的同步調(diào)用。
在 Java 早期版本中畔塔,synchronized 屬于重量級鎖潭辈,效率低下,因為監(jiān)視器鎖(monitor)是依賴于底層的操作系統(tǒng)的 Mutex Lock 來實現(xiàn)的澈吨,Java 的線程是映射到操作系統(tǒng)的原生線程之上的把敢。如果要掛起或者喚醒一個線程,都需要操作系統(tǒng)幫忙完成谅辣,而操作系統(tǒng)實現(xiàn)線程之間的切換時需要從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)技竟,這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,時間成本相對較高屈藐,這也是為什么早期的 synchronized 效率低的原因。慶幸的是在 Java 6 之后 Java 官方對從 JVM 層面對synchronized 較大優(yōu)化熙尉,所以現(xiàn)在的 synchronized 鎖效率也優(yōu)化得很不錯了联逻。JDK1.6對鎖的實現(xiàn)引入了大量的優(yōu)化,如自旋鎖检痰、適應(yīng)性自旋鎖包归、鎖消除、鎖粗化铅歼、偏向鎖公壤、輕量級鎖等技術(shù)來減少鎖操作的開銷。[1]
- Java對象頭
存儲兩部分信息
- 對象自身的運(yùn)行時數(shù)據(jù)椎椰,
- HashCode
- GC分代年齡
- 鎖狀態(tài)標(biāo)志
- 是否偏向鎖標(biāo)記位
- 偏向線程ID
這部分?jǐn)?shù)據(jù)的長度在32位和64位的虛擬機(jī)中分別為32bit和64bit厦幅,也叫做Mark Word】考慮到虛擬機(jī)的空間效率确憨,Mark Word被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu)以便在極小的空間內(nèi)存儲盡量多的信息译荞。根據(jù)鎖狀態(tài)的不同存儲的信息也不同,見下圖所示:
在32位的HotSpot虛擬機(jī)中休弃,對象未被鎖定的狀態(tài)下吞歼,Mark Word的32bit空間中的25bit用于存儲對象HashCode,4bit用于存儲對象分代年齡塔猾,2bit用于存儲鎖標(biāo)志位篙骡,1bit固定為0。
上圖也展示了在其他狀態(tài)下(輕量級鎖定丈甸、重量級鎖定糯俗、GC標(biāo)記、可偏向)下對象的存儲內(nèi)容老虫。
- 存儲指向方法區(qū)對象類型數(shù)據(jù)的指針叶骨,如果是數(shù)組對象的話,還會有一個額外的部分用于存儲數(shù)組長度祈匙。
重量級鎖
叫重量級的原因忽刽,是因為在多線程競爭下,需要操作系統(tǒng)互斥量mutex
夺欲,這種同步方式的成本非常高跪帝,包括系統(tǒng)調(diào)用引起的內(nèi)核態(tài)與用戶態(tài)切換
、線程阻塞
造成的線程切換等些阅。因此伞剑,后來稱這種鎖為“重量級鎖”。
- 鎖的升級
鎖有四種狀態(tài)市埋,從低到高依次是:無鎖狀態(tài)黎泣、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài)缤谎。鎖可以升級不能降級抒倚。
- 輕量級鎖
-
在代碼進(jìn)入同步塊時,如果同步對象沒有被鎖定坷澡,即Mark word處于無鎖狀態(tài)(鎖標(biāo)志位為“01”狀態(tài)托呕,是否為偏向鎖為“0”),虛擬機(jī)將在當(dāng)前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間频敛,用于存儲鎖對象目前的Mark Word的拷貝项郊,官方把這個拷貝加了一個Displaced前綴,即Displaced Mark Word斟赚,這時候線程堆棧與對象頭的狀態(tài)如下圖所示
在這里插入圖片描述
-
-
使用CSA操作將對象的Mark Word更新為指向Lock Record的指針着降,如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖拗军。對象Mark Down的鎖標(biāo)志位(Mark Word的最后2bit)轉(zhuǎn)變成"00"鹊碍,這時候線程堆棧與對象頭的狀態(tài)如下所示:
在這里插入圖片描述 - 如果
更新操作失敗
了厌殉,==虛擬機(jī)首先會檢查對象的Mark Word是否指向當(dāng)前線程的棧幀==。否則說明這個對象鎖已經(jīng)被其他線程占用了侈咕,這時候自旋等待公罕,如果還是沒有拿到鎖,輕量級鎖膨脹為重量級鎖耀销,鎖標(biāo)志的狀態(tài)值變?yōu)椤?0”楼眷,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)熊尉。
這里我的理解是這樣的罐柳,如果線程要獲取的對象鎖是處于無鎖狀態(tài),那么到這一步更新操作失敗了狰住,已經(jīng)能夠說明是有別的線程在一起競爭张吉,這里的檢查Mark Word肯定是會指向別的線程的,不知道為什么書中和網(wǎng)上的資料都說第一步會檢查是否指向當(dāng)前線程催植。原因可能是如果當(dāng)前線程要獲取的對象鎖不是無鎖狀態(tài)肮蛹,而是處于偏向鎖狀態(tài)了,那么第一步肯定是檢查Mark Word是否指向了自己線程的棧幀创南,如果是伦忠,說明當(dāng)前線程已經(jīng)擁有了這個鎖了,直接進(jìn)入同步塊執(zhí)行稿辙。
Java Synchronized原理圖見下圖[2]
所有的鎖見下圖[3]
[圖片上傳失敗...(image-484f37-1551705563425)]