且聽我一個(gè)故事講透一個(gè)鎖原理之synchronized

微信公眾號(hào):IT一刻鐘
大型現(xiàn)實(shí)非嚴(yán)肅主義現(xiàn)場(chǎng)
一刻鐘與你分享優(yōu)質(zhì)技術(shù)架構(gòu)與見聞又碌,做一個(gè)有劇情的程序員
關(guān)注可第一時(shí)間了解更多精彩內(nèi)容,定期有福利相送喲注整。

故事從這里展開

蜀國(guó)有一個(gè)皇帝叫蜀道難串前,他比較難伺候,別的皇帝早朝都是在大殿上同時(shí)接見所有大臣轩猩,共商國(guó)是卷扮。他不一樣,他說早朝你們不要有事沒事都跑過來嘰嘰喳喳均践,有事則來晤锹,無事則該干啥干啥去,然后安排太監(jiān)每天早上在大門口守著彤委,每次只允許一個(gè)大臣進(jìn)來匯報(bào)情況鞭铆。
“你敢多放進(jìn)來一個(gè)就砍腦袋的干活〗褂埃“
太監(jiān)趕緊下跪车遂,說“謫!“斯辰。
第一天舶担,太監(jiān)傳話欽天監(jiān)求見,皇帝允了彬呻,欽天監(jiān)上殿報(bào)曰:”臣稟報(bào)衣陶,昨日我司夜觀星象,西方忽現(xiàn)王星忽明忽暗闸氮,恐戎狄那邊有亂剪况。“
“朕知道了蒲跨,退下吧”译断。一日無事。
第二天或悲,太監(jiān)傳話欽天監(jiān)求見孙咪,皇帝允了藏姐。一日無事。
第三天该贾,太監(jiān)傳話欽天監(jiān)求見......一日無事羔杨。
第四天,欽天監(jiān)......一日無事杨蛋。
第五天兜材,皇帝不耐煩了,和賈太監(jiān)說逞力,欽天監(jiān)這老家伙整天是不是閑著沒事曙寡,以后他來了不用給我稟報(bào),直接放他上殿講寇荧,講完讓他走吧举庶。
國(guó)泰民安的日子依舊過著,每天只有欽天監(jiān)一個(gè)人來報(bào)告揩抡,賈太監(jiān)每次看到是欽天監(jiān)來了户侥,也懶得搭理了,直接放他進(jìn)去了峦嗤。(這就是偏向鎖蕊唐,稍后我細(xì)細(xì)道來)
又一日,欽天監(jiān)如往常進(jìn)殿報(bào)道烁设,賈太監(jiān)站在門口打著盹替梨,忽然耳邊傳來一個(gè)聲音:
“賈太監(jiān),幫我稟告圣上装黑,工部李尚書求見副瀑。”
“emmm...進(jìn)去吧...嗯恋谭?等等糠睡,尚書大人你先等等,欽天監(jiān)在里面箕别,你等會(huì)再來求見吧铜幽≈托唬”太監(jiān)一陣后怕串稀,尋思著欽天監(jiān)還在里面呢,這要是放進(jìn)去了狮杨,我這腦袋可就沒了母截,果然嗜睡誤事。
過了一會(huì)兒橄教,李尚書回來詢問求見清寇,被告知?dú)J天監(jiān)還沒走喘漏,只好又離去。
又過了一會(huì)兒华烟,李尚書又回來詢問求見翩迈,正巧欽天監(jiān)走了,太監(jiān)進(jìn)殿傳話說工部李尚書求見盔夜,皇帝宣覲見负饲,李尚書進(jìn)殿上報(bào)了一番東南連連大雨,已派人去監(jiān)察水利喂链,修繕河堤返十。(這就是輕量級(jí)鎖)
忽一日,西戎狄和北匈奴同時(shí)對(duì)帝國(guó)西方和北方發(fā)難椭微,前線戰(zhàn)事消息如片片雪花紛紛涌入京城洞坑,瞬間殿外來了一群大臣有要事稟告。
一會(huì)兒這個(gè)來問賈公公我可以進(jìn)去了嗎蝇率?一會(huì)兒那個(gè)來問賈公公我可以進(jìn)去了嗎迟杂?
把賈太監(jiān)累的喲,一天下來光說“稍后再來”都把嘴皮子磨破了本慕,沒幾日逢慌,賈太監(jiān)就跪在皇帝面前哭泣道:“圣上啊溃卡,快想想辦法呀旺垒,奴才這身子骨就要交代在門口了寿桨〔冀危”
皇帝一聽棱烂,說你傻啊慕的,叫他們一個(gè)個(gè)在門外排隊(duì)啊燥滑,誰叫你要他們稍后來求見的肆良。
賈太監(jiān)細(xì)思大喜纺弊,覺得有理牛欢,次日在門口豎起一個(gè)牌子“稟報(bào)要事者,這邊排隊(duì)”淆游,賈太監(jiān)再也不用一個(gè)人對(duì)著一群人反復(fù)回話傍睹,只需要每次出來一個(gè),然后傳話放進(jìn)去一個(gè)犹菱,就可以了拾稳。(這就是重量級(jí)鎖)
上面這個(gè)故事,分別講述了synchronized內(nèi)部四種級(jí)別的狀態(tài)腊脱,分別是:無鎖狀態(tài)访得,偏向鎖狀態(tài),輕量級(jí)鎖狀態(tài)陕凹,重量級(jí)鎖狀態(tài)悍抑。

重量級(jí)鎖狀態(tài)

我們首先從重量級(jí)鎖開始講鳄炉,重量級(jí)鎖是通過互斥量(Mutex)來實(shí)現(xiàn)的,即一個(gè)線程進(jìn)入了synchronized同步塊搜骡,在未完成任務(wù)時(shí)拂盯,會(huì)阻塞后面的所有線程。
就像上面的故事所講的记靡,要稟告要事的大臣只能在大殿門口外一個(gè)接一個(gè)的阻塞排隊(duì)磕仅。
之所以稱它為重量級(jí)鎖,是因?yàn)镴ava線程是映射到操作系統(tǒng)的原生線程上的簸呈,如果要阻塞或喚醒一個(gè)線程榕订,都需要依靠操作系統(tǒng)從當(dāng)前用戶態(tài)轉(zhuǎn)換到核心態(tài)中,這種狀態(tài)轉(zhuǎn)換需要耗費(fèi)處理器很多時(shí)間蜕便,對(duì)于簡(jiǎn)單同步塊劫恒,可能狀態(tài)轉(zhuǎn)換時(shí)間比用戶代碼執(zhí)行時(shí)間還要長(zhǎng),導(dǎo)致實(shí)際業(yè)務(wù)處理所占比偏小轿腺,性能損失較大两嘴。
當(dāng)然這個(gè)在虛擬機(jī)層面進(jìn)行了一些比如自旋等待,鎖粗化等等的優(yōu)化族壳,避免陷入頻繁的切換狀態(tài)憔辫。在這里我就不細(xì)講了,有興趣的可以關(guān)注我仿荆,我后續(xù)再和各位看官講上一講贰您。

輕量級(jí)鎖狀態(tài)

輕量級(jí)鎖是JDK6引入的,它的輕量是相較于通過系統(tǒng)互斥量實(shí)現(xiàn)的傳統(tǒng)鎖拢操,輕量鎖并不是用來取代重量級(jí)鎖的锦亦,而是在沒有大量線程競(jìng)爭(zhēng)的情況下,減少系統(tǒng)互斥量的使用令境,降低性能的損耗杠园。
輕量級(jí)鎖是通過CAS(Compare And Swap)機(jī)制實(shí)現(xiàn)的,即如果鎖被其他線程所占用舔庶,當(dāng)前線程會(huì)通過自旋來獲取鎖抛蚁,從而避免用戶態(tài)與核心態(tài)的轉(zhuǎn)換。
就像上面故事所說的惕橙,大殿中欽天監(jiān)在匯報(bào)工作瞧甩,工部尚書要求見,并不需要賈太監(jiān)每次都進(jìn)去問一下皇帝吕漂,惹得皇帝龍顏大怒亲配,而是大臣自己隔一段時(shí)間便來詢問賈太監(jiān)能不能進(jìn)去尘应,不能就稍后再來問惶凝,直到可以進(jìn)去為止吼虎。

偏向鎖狀態(tài)

偏向鎖也是JDK6引入的,它存在的依據(jù)是“大多數(shù)情況下苍鲜,鎖不僅不存在多線程競(jìng)爭(zhēng)思灰,而且總是由同一線程多次獲得”。它是通過記錄第一次進(jìn)入同步塊的線程id來實(shí)現(xiàn)的混滔,如果下一個(gè)要進(jìn)入同步塊的線程與記錄的線程id相同洒疚,則說明這個(gè)鎖由此線程占有,可以直接進(jìn)入到同步塊坯屿,不用執(zhí)行CAS油湖。
就像故事中的,如果每天只有欽天監(jiān)一個(gè)人來的話领跛,就不用賈太監(jiān)稟告了乏德,賈太監(jiān)每次一看到欽天監(jiān),尋思著吠昭,喲喊括,欽天監(jiān)呢,您自個(gè)兒直接進(jìn)去吧矢棚,說完自個(gè)兒出來吧郑什。
如果說輕量鎖是為了消除系統(tǒng)互斥量帶來的性能損耗,那么偏向鎖就是為了消除CAS帶來的性能損耗蒲肋,使之在無競(jìng)爭(zhēng)的情況下消除整個(gè)同步蘑拯,性能無限接近非同步。

如何通過這四種狀態(tài)實(shí)現(xiàn)性能大幅度提升的

Java對(duì)象頭

要說這個(gè)問題兜粘,我們需要先講一下Java對(duì)象頭强胰,每個(gè)對(duì)象都會(huì)有一個(gè)對(duì)象頭,它分為三個(gè)部分:

內(nèi)容 說明
Mark Word 存儲(chǔ)對(duì)象的hashcode或鎖信息
Class Metadata Address 存儲(chǔ)到對(duì)象類型數(shù)據(jù)的指針
Array length 數(shù)組的長(zhǎng)度(如果當(dāng)前對(duì)象是數(shù)組)

從表格可見妹沙,synchronized鎖的信息是存在對(duì)象頭里一個(gè)叫Mark Word的區(qū)域里的偶洋,考慮到虛擬機(jī)的空間效率,Mark Word被設(shè)計(jì)成非固定的數(shù)據(jù)結(jié)構(gòu)距糖,會(huì)根據(jù)對(duì)象的狀態(tài)復(fù)用存儲(chǔ)空間來存儲(chǔ)不同的內(nèi)容:


鎖的升級(jí)

當(dāng)JVM啟用了偏向鎖模式(JDK6以上默認(rèn)開啟)玄窝,新創(chuàng)建對(duì)象的Mark Word是未鎖定,未偏向但可偏向狀態(tài)悍引,此時(shí)Mark Word中的Thread id為0恩脂,表示未偏向任何線程,也叫做匿名偏向(anonymously biased)趣斤。

偏向鎖狀態(tài)--->無鎖不可偏向狀態(tài)/輕量級(jí)鎖狀態(tài)

當(dāng)?shù)谝粋€(gè)線程嘗試進(jìn)入同步塊時(shí)俩块,發(fā)現(xiàn)Mark Word中線程ID為0,則會(huì)使用CAS將自己的線程ID設(shè)置到Mark Word中,并且玉凯,在當(dāng)前線程棧中由高到低順序找到可用的Lock Record势腮,將線程ID記錄下。完成這些漫仆,此線程就獲取了鎖對(duì)象的偏向鎖捎拯。
當(dāng)該偏向線程再次進(jìn)入同步塊時(shí),發(fā)現(xiàn)鎖對(duì)象偏向的就是當(dāng)前線程盲厌,會(huì)往當(dāng)前線程的棧中添加一條Displaced Mark Word為空的Lock Record中署照,用來統(tǒng)計(jì)重入的次數(shù),然后繼續(xù)執(zhí)行同步塊代碼吗浩,因?yàn)榫€程棧是私有的建芙,不需要CAS指令進(jìn)行操作,所以在偏向鎖模式下懂扼,同一個(gè)線程岁钓,只會(huì)執(zhí)行一個(gè)CAS,之后獲取釋放鎖只需要對(duì)Lock Record做操作微王,性能損耗基本可以忽略屡限。
當(dāng)另外一個(gè)線程試圖進(jìn)入同步塊時(shí),發(fā)現(xiàn)Mark Word中線程ID與自己不相符炕倘,這個(gè)時(shí)候就會(huì)引發(fā)偏向鎖的撤銷钧大,變成無鎖不可偏向狀態(tài)或輕量級(jí)鎖狀態(tài),當(dāng)然罩旋,這只是宏觀上的描述啊央,嚴(yán)格意義上講是不準(zhǔn)確的,因?yàn)槔锩孢€存在重偏向機(jī)制涨醋,這里就不過于深入瓜饥,在后續(xù)的文章中,我會(huì)專門出一篇文章浴骂,給各位看官詳細(xì)介紹偏向鎖到底是怎么回事乓土。

無鎖不可偏向狀態(tài)--->輕量級(jí)鎖狀態(tài)

當(dāng)鎖對(duì)象變成無鎖不可偏向狀態(tài)時(shí),多個(gè)線程運(yùn)行到同步塊以后溯警,會(huì)檢查鎖對(duì)象狀態(tài)值標(biāo)志是否加鎖趣苏,如果沒有鎖,就把鎖對(duì)象的Mark Word信息拷貝存儲(chǔ)到當(dāng)前線程棧楨中Lock Record里梯轻,然后通過CAS嘗試把對(duì)象的Mark Word的值改變成一個(gè)指向自己線程的指針食磕。如果成功,則當(dāng)前線程獲得鎖對(duì)象的輕量級(jí)鎖喳挑,其他線程的CAS就會(huì)失敗彬伦,因?yàn)殒i對(duì)象的Mark Word已經(jīng)變成一個(gè)新的指針了滔悉,必須等待線程釋放鎖,此時(shí)其他線程則通過自旋來競(jìng)爭(zhēng)鎖单绑。當(dāng)獲取鎖的線程執(zhí)行完畢釋放鎖的時(shí)候回官,會(huì)將Lock Record里面之前拷貝的值還原到鎖對(duì)象的Mark Word中。

輕量級(jí)鎖狀態(tài)--->重量級(jí)鎖狀態(tài)

當(dāng)自旋次數(shù)超過JVM預(yù)期上限询张,會(huì)影響性能孙乖,所以競(jìng)爭(zhēng)的線程就會(huì)把鎖對(duì)象的Mark Word指向重鎖浙炼,所謂的重鎖份氧,實(shí)際上就是一個(gè)堆上的monitor對(duì)象,即弯屈,重量級(jí)鎖的狀態(tài)下蜗帜,對(duì)象的Mark Word為指向一個(gè)堆中monitor對(duì)象的指針。
然后所有的競(jìng)爭(zhēng)線程放棄自旋资厉,逐個(gè)插入到monitor對(duì)象里的一個(gè)隊(duì)列尾部厅缺,進(jìn)入阻塞狀態(tài)。
當(dāng)成功獲取輕量級(jí)鎖的線程執(zhí)行完畢宴偿,嘗試通過CAS釋放鎖時(shí)湘捎,因?yàn)镸ark Word已經(jīng)指向重鎖,導(dǎo)致輕量級(jí)鎖釋放失敗窄刘,這時(shí)線程就會(huì)知道鎖已經(jīng)升級(jí)為重量級(jí)鎖窥妇, 它不僅要釋放當(dāng)前鎖,還要喚醒其他阻塞的線程來重新競(jìng)爭(zhēng)鎖娩践。
大概流程如下圖所示:



這里有一點(diǎn)需注意的是:鎖只能升級(jí)活翩,不能降級(jí)。

鎖的對(duì)比

優(yōu)點(diǎn) 缺點(diǎn) 適用場(chǎng)景
偏向鎖 加鎖和解鎖不需要額外的消耗翻伺,和執(zhí)行非同步方法相比僅存在納秒級(jí)的差距 如果線程間存在鎖競(jìng)爭(zhēng)材泄,會(huì)帶來額外的鎖撤銷的消耗 適用于只有一個(gè)線程訪問同步塊場(chǎng)景
輕量級(jí)鎖 競(jìng)爭(zhēng)的線程不會(huì)堵塞,提高了程序的響音速度 始終得不到鎖的線程吨岭,使用自旋會(huì)消耗CPU 追求響應(yīng)時(shí)間拉宗,同步塊執(zhí)行速度非常快
重量級(jí)鎖 線程競(jìng)爭(zhēng)不使用自旋辣辫,不會(huì)消耗CPU 線程阻塞簿废,響應(yīng)時(shí)間緩慢 追求吞吐量,同步塊執(zhí)行速度較慢

synchronized的底層實(shí)現(xiàn)

synchronized無非以下兩種:
1.對(duì)象鎖:修飾非靜態(tài)方法络它,修飾代碼塊
2.類鎖:修飾靜態(tài)方法族檬,修飾代碼塊
其中按照修飾類型來分,又可以分為代碼塊同步和方法同步

代碼塊同步

代碼塊同步鎖的是對(duì)象化戳,使用monitorenter和monitorexit指令實(shí)現(xiàn)的单料。雖然我知道多一行代碼少一位看官的定理埋凯,但是這里還是必須貼一張代碼圖,來證明我沒有瞎說扫尖,是有理有據(jù)的“理據(jù)服”白对。
想要降服妖怪,就得先將其打回原形换怖,所以我們先對(duì)一段簡(jiǎn)單的代碼進(jìn)行反編譯甩恼,得到它的字節(jié)碼。

    final Object lock = new Object();
    public int subtr(int i){
        synchronized (lock){
            return i-1;
        }
    }

字節(jié)碼:


對(duì)象鎖反編譯.png

可以看出沉颂,monitorenter指令是在編譯后插入到同步代碼塊的開始位置条摸,monitorexit插入到同步代碼塊結(jié)束的地方,正常情況下monitorenter和monitorexit是一對(duì)一的匹配铸屉,而后面又出現(xiàn)了一個(gè)monitorexit钉蒲,是因?yàn)槟抢锸钱惓L帲脕肀WC方法執(zhí)行異常的時(shí)候彻坛,可以自動(dòng)解鎖顷啼,而不會(huì)造成死鎖。

方法同步

方法同步的實(shí)現(xiàn)官方?jīng)]有透露昌屉,我們嘗試對(duì)一個(gè)方法同步的代碼進(jìn)行反編譯钙蒙。

    public synchronized int add(int i){
        return i+1;
    }

字節(jié)碼:


同步方法反編譯.png

從字節(jié)碼里也看不到monitorenter和monitorexit,智能發(fā)現(xiàn)flags那里间驮,多了一個(gè)ACC_SYNCHRONIZED的標(biāo)示躬厌,沒什么頭緒。不過我猜想蜻牢,底層應(yīng)該是鎖方法所屬的對(duì)象或類烤咧。

這就是synchronized的大致原理,打回原形之后來看抢呆,是不是就覺得也不過如此煮嫌?有什么疑問或更好的解讀,可以在下方留言抱虐,我們進(jìn)行愉快友好的磋商交流昌阿。
如果覺得有用,記得分享~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末恳邀,一起剝皮案震驚了整個(gè)濱河市懦冰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌谣沸,老刑警劉巖刷钢,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乳附,居然都是意外死亡内地,警方通過查閱死者的電腦和手機(jī)伴澄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阱缓,“玉大人非凌,你說我怎么就攤上這事【U耄” “怎么了敞嗡?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)航背。 經(jīng)常有香客問我喉悴,道長(zhǎng),這世上最難降的妖魔是什么沃粗? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任粥惧,我火速辦了婚禮键畴,結(jié)果婚禮上最盅,老公的妹妹穿的比我還像新娘。我一直安慰自己起惕,他們只是感情好涡贱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著惹想,像睡著了一般问词。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上嘀粱,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天激挪,我揣著相機(jī)與錄音,去河邊找鬼锋叨。 笑死垄分,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娃磺。 我是一名探鬼主播薄湿,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼偷卧!你這毒婦竟也來了豺瘤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤听诸,失蹤者是張志新(化名)和其女友劉穎坐求,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌梨,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡桥嗤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年赛糟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砸逊。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡璧南,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师逸,到底是詐尸還是另有隱情司倚,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布篓像,位于F島的核電站动知,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏员辩。R本人自食惡果不足惜盒粮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奠滑。 院中可真熱鬧丹皱,春花似錦、人聲如沸宋税。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杰赛。三九已至呢簸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乏屯,已是汗流浹背根时。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辰晕,地道東北人蛤迎。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像伞芹,于是被迫代替她去往敵國(guó)和親忘苛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容