Synchronized 天天用,实现原理你懂吗?
來源:小小木的博客
www.cnblogs.com/wyc1994666/p/11748212.html
Synchronized?關(guān)鍵字算是Java的元老級(jí)鎖了,一開始它撐起了Java的同步任務(wù),其用法簡單粗暴容易上手。但是有些與它相關(guān)的知識(shí)點(diǎn)還是需要我們開發(fā)者去深入掌握的。
比如,我們都知道通過?Synchronized?鎖來實(shí)現(xiàn)互斥功能,可以用在方法或者代碼塊上,那么不同用法都是怎么實(shí)現(xiàn)的,以及都經(jīng)歷了了哪些優(yōu)化等等問題都需要我們?cè)鷮?shí)的理解。
-
1.基本用法
-
2.實(shí)現(xiàn)原理
-
2.1 同步代碼塊的實(shí)現(xiàn)
-
2.2 同步方法的實(shí)現(xiàn)
-
3.鎖升級(jí)
-
3.1 Java對(duì)象頭介紹
-
3.2 什么是鎖升級(jí)
1.基本用法
通常我們可以把?Synchronized?用在一個(gè)方法或者代碼塊里,方法又有普通方法或者靜態(tài)方法。
對(duì)于普通同步方法,鎖是當(dāng)前實(shí)例對(duì)象,也就是this
public?class?TestSyn{private?int?i=0;public?synchronized?void?incr(){i++;} }對(duì)于靜態(tài)同步方法,鎖是Class對(duì)象
public?class?TestSyn{private?static?int?i=0;public?static?synchronized?void?incr(){i++;} }??對(duì)于同步代碼塊,鎖是同步代碼塊里的對(duì)象
public?class?TestSyn{private??int?i=0;Object?o?=?new?Object();public??void?incr(){synchronized(o){i++;}} }2.實(shí)現(xiàn)原理
在JVM規(guī)范中介紹了?Synchronized?的實(shí)現(xiàn)原理,JVM基于進(jìn)入和退出Monitor對(duì)
象來實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣。
代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,而方法同步是使用另外一種方式實(shí)現(xiàn)的,通過一個(gè)方法標(biāo)志(flag) ACC_SYNCHRONIZED來實(shí)現(xiàn)的。
2.1 同步代碼塊的實(shí)現(xiàn)
monitorenter 和 monitorexit
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.monitorenter (參考來源)
下面看下JVM規(guī)范里對(duì)moniterenter 和 monitorexit的介紹
Each object has a monitor associated with it. The thread that executes monitorenter gains ownership of the monitor associated with objectref. If another thread already owns the monitor associated with objectref, the current thread waits until the object is unlocked,
每個(gè)對(duì)象都有一個(gè)監(jiān)視器(Moniter)與它相關(guān)聯(lián),執(zhí)行moniterenter指令的線程將獲得與objectref關(guān)聯(lián)的監(jiān)視器的所有權(quán),如果另一個(gè)線程已經(jīng)擁有與objectref關(guān)聯(lián)的監(jiān)視器,則當(dāng)前線程將等待直到對(duì)象被解鎖為止。
A monitorenter instruction may be used with one or more monitorexit instructions to implement a synchronized statement in the Java programming language. The monitorenter and monitorexit instructions are not used in the implementation of synchronized methods
重點(diǎn)來了,上面這段介紹了兩點(diǎn):
-
通過monitorenter和monitorexit指令來實(shí)現(xiàn)Java語言的同步代碼塊(后面有代碼示例)
-
monitorenter和monitorexit指令沒有被用在同步方法上!!!
2.2 同步方法的實(shí)現(xiàn)
先看下JVM規(guī)范里怎么說的 https://docs.oracle.com/javase/specs/jvms/se6/html/Compiling.doc.html#6530 (參考來源)
A synchronized method is not normally implemented using monitorenter and monitorexit. Rather, it is simply distinguished in the runtime constant pool by the ACC_SYNCHRONIZED flag, which is checked by the method invocation instructions. When invoking a method for which ACC_SYNCHRONIZED is set, the current thread acquires a monitor, invokes the method itself, and releases the monitor whether the method invocation completes normally or abruptly.
上面這段話主要講了幾點(diǎn):
- 同步方法的實(shí)現(xiàn)不是基于monitorenter和monitorexit指令來實(shí)現(xiàn)的
- 在運(yùn)行時(shí)常量池里通過ACC_SYNCHRONIZED來區(qū)分是否是同步方法,方法執(zhí)行時(shí)會(huì)檢查該標(biāo)志
- 當(dāng)一個(gè)方法有這個(gè)標(biāo)志的時(shí)候,進(jìn)入的線程首先需要獲得監(jiān)視器才能執(zhí)行該方法
- 方法結(jié)束或者拋異常時(shí)會(huì)釋放監(jiān)視器
可以通過反編譯字節(jié)碼來查看底層是怎么實(shí)現(xiàn)的
//?得到字節(jié)碼javac?TestSyn.java //?反編譯字節(jié)碼javap?-v?TestSyn.class同步代碼塊的反編譯結(jié)果如下:
同步方法的反編譯結(jié)果如下:
3.鎖升級(jí)
3.1 Java對(duì)象頭介紹
上面這段話主要講了幾點(diǎn):
同步方法的實(shí)現(xiàn)不是基于monitorenter和monitorexit指令來實(shí)現(xiàn)的
在運(yùn)行時(shí)常量池里通過ACC_SYNCHRONIZED來區(qū)分是否是同步方法,方法執(zhí)行時(shí)會(huì)檢查該標(biāo)志
當(dāng)一個(gè)方法有這個(gè)標(biāo)志的時(shí)候,進(jìn)入的線程首先需要獲得監(jiān)視器才能執(zhí)行該方法
方法結(jié)束或者拋異常時(shí)會(huì)釋放監(jiān)視器
對(duì)象的內(nèi)存布局
對(duì)象頭的結(jié)構(gòu)表示如下圖:
mark word的表示如下圖:
3.2 什么是鎖升級(jí)
下面舉個(gè)搶茅坑的例子來解釋一下鎖升級(jí)過程。
當(dāng)只有一個(gè)線程訪問時(shí)叫做偏向鎖
假設(shè)我們每個(gè)廁所都有一把鑰匙,要想使用廁所首先必須得獲得鎖。某天上午員工甲急急忙忙的打完卡上廁所了,并在廁所門上貼了 “工號(hào)007使用中”的標(biāo)簽,說明目前被工號(hào)007(相當(dāng)于線程id)的員工占用呢,他再次向進(jìn)入的時(shí)候只要上面的標(biāo)簽還顯示工號(hào)007,他自己可以隨便進(jìn)入,不需要再次上鎖了,有點(diǎn)偏向工號(hào)007員工的意思,所以這叫偏向鎖。
發(fā)生競爭的時(shí)候升級(jí)成輕量級(jí)鎖 (自旋等待)
員工甲正在使用廁所的時(shí)候,又來了兩個(gè)人想用廁所,但發(fā)現(xiàn)廁所被人使用著呢,無法獲得鎖。所以只能在外面等著甲出來,他們等的過程叫做“自旋”,這個(gè)叫做輕量級(jí)鎖。
那么又有一個(gè)問題,當(dāng)甲出來之后正等著的那兩個(gè)人誰活得鎖呢?有兩種方式,按到達(dá)的順序來排隊(duì)或者不排隊(duì),這兩種都可以實(shí)現(xiàn),前者叫做公平鎖,后者叫做非公平鎖。
自旋等待沒結(jié)果的時(shí)候升級(jí)成重量級(jí)鎖
但那兩個(gè)人自旋一段時(shí)間之后發(fā)現(xiàn)甲還沒出來(JDK1.6規(guī)定為10次),一直這么等也不是個(gè)法子啊,所以打算向上升級(jí),找?guī)芾韱T(操作系統(tǒng))反饋,升級(jí)成了重量級(jí)鎖了。
鎖的狀態(tài)總共有四種,無鎖狀態(tài)、偏向鎖、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競爭,鎖可以從偏向鎖升級(jí)到輕量級(jí)鎖,再升級(jí)的重量級(jí)鎖。另外關(guān)注公眾號(hào)Java技術(shù)棧回復(fù)JVM46獲取一份46頁的JVM調(diào)優(yōu)教程。
鎖升級(jí)過程中mark word的變化如下:
偏向鎖
偏向鎖也是JDK 1.6中引入的一項(xiàng)鎖優(yōu)化, 引入它是為了優(yōu)化在沒有鎖競爭場景下的鎖消除。比如一段同步代碼一直是由單個(gè)線程調(diào)用,在這種場景下就沒必要使用同步鎖了,這里指的同步鎖不是指 synchronized,而是說沒不要到操作系統(tǒng)層面的互斥量了。
偏向鎖的偏向是指該同步代碼會(huì)一直偏向第一個(gè)調(diào)用它的線程,直到有別的線程過來競爭這把鎖,在第一次調(diào)用同步代碼并獲得鎖時(shí)會(huì)在對(duì)象頭和棧幀鎖記錄行(Lock Record)里存儲(chǔ)偏向線程Id,該線程在此進(jìn)入的時(shí)候就不需要重新申請(qǐng)鎖了。只需檢測對(duì)象頭的Mark Word里是否存儲(chǔ)著指向該線程的ID即可。
直到又有線程來競爭這把鎖的時(shí)候偏向鎖會(huì)撤銷偏向。
輕量級(jí)鎖
輕量級(jí)鎖是JDK 1.6之中加入的新型鎖機(jī)制, 它名字中的“輕量級(jí)”是相對(duì)于使用操作系統(tǒng)。
互斥量來實(shí)現(xiàn)的傳統(tǒng)鎖而言的, 因此傳統(tǒng)的鎖機(jī)制就稱為“重量級(jí)”鎖。它并不是用來代替重量級(jí)鎖的, 它的本意是在統(tǒng)的重量級(jí)鎖使用操作系統(tǒng)互斥量產(chǎn)生的性能消耗。
線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中,官方稱為Displaced Mark Word。
然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當(dāng)前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當(dāng)前線程便嘗試使用自旋來獲取鎖.一直原地自旋,如果自旋數(shù)達(dá)到10次了則升級(jí)為重量級(jí)鎖。
重量級(jí)鎖
競爭的線程自旋一段時(shí)間未能獲取鎖之后會(huì)升級(jí)為重量級(jí)鎖,這個(gè)時(shí)候鎖的獲取與釋放都會(huì)由操作系統(tǒng)來分配了,如果持有鎖的線程釋放鎖之后操作系統(tǒng)會(huì)喚醒所有阻塞的哪些線程,并進(jìn)入新一輪的爭搶模式,需要注意的是這些阻塞的線程沒有獲得鎖的優(yōu)先級(jí),也就是說synchronized鎖是非公平的。
除此之外synchronized對(duì)中斷操作也是無感的,不會(huì)因?yàn)楸恢袛喽艞壸枞却?#xff0c;它要么得到鎖要么一直阻塞。
總結(jié)
以上是生活随笔為你收集整理的Synchronized 天天用,实现原理你懂吗?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Caffeine Cache~高性能 J
- 下一篇: 左耳朵耗子:程序员如何把控自己的职业?