關(guān)鍵字 synchronized

Synchronized和lock區(qū)別

ReentrantLock可重入鎖的使用

一申眼、簡(jiǎn)述

synchronized 是一把經(jīng)典的 JVM 級(jí)別的鎖。在加了它的方法猾昆、代碼塊中态蒂,一次只允許一個(gè)線程進(jìn)入特定代碼段,從而避免多線程同時(shí)修改同一數(shù)據(jù)辽社。在 JDK6 之前伟墙,syncronized 是一把重量級(jí)的鎖,隨著 JDK 的升級(jí)滴铅,不斷的優(yōu)化戳葵,如今它變得不那么重了,甚至在某些場(chǎng)景下汉匙,它的性能反而優(yōu)于輕量級(jí)鎖拱烁。實(shí)現(xiàn)原理就是鎖升級(jí)的過(guò)程。

1??synchronized 的作用

  1. 原子性:保證語(yǔ)句塊內(nèi)操作是原子的噩翠。
  2. 可見(jiàn)性:保證可見(jiàn)性(通過(guò)“在執(zhí)行 unlock 之前戏自,必須先把此變量同步回主內(nèi)存”實(shí)現(xiàn))。
  3. 有序性:保證有序性(通過(guò)“一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行 lock 操作”)伤锚。

2??synchronized 的使用

  1. 修飾實(shí)例方法擅笔,對(duì)當(dāng)前實(shí)例對(duì)象加鎖。
  2. 修飾靜態(tài)方法屯援,多當(dāng)前類的 Class 對(duì)象加鎖猛们。
  3. 修飾代碼塊,對(duì) synchronized 括號(hào)內(nèi)的對(duì)象加鎖狞洋。

二弯淘、實(shí)現(xiàn)原理

JVM 的同步(synchronized)基于進(jìn)入和退出 Monitor 對(duì)象(即對(duì)象的監(jiān)視器,虛擬機(jī)規(guī)范中用的是管程一詞)實(shí)現(xiàn)吉懊,無(wú)論是顯式同步(有明確的 monitorenter 和 monitorexit 指令耳胎,即同步代碼塊)還是隱式同步都是如此惯吕。synchronized 是可重入的,所以不會(huì)自己把自己鎖死怕午。

1??代碼塊的同步顯示同步
利用 monitorenter 和 monitorexit 這兩個(gè)字節(jié)碼指令废登,配合完成了 synchronized 修飾代碼塊的互斥訪問(wèn)。

  1. 被 synchronized 修飾的代碼塊郁惜,編譯器編譯后:在代碼開(kāi)始加入了 monitorenter堡距,在代碼后面加入了 monitorexit。
  2. 在虛擬機(jī)執(zhí)行到 monitorenter 指令的時(shí)候兆蕉,會(huì)請(qǐng)求獲取對(duì)象的 monitor 鎖羽戒,基于 monitor 鎖又衍生出一個(gè)鎖計(jì)數(shù)器的概念。
  3. 當(dāng)執(zhí)行 monitorenter 時(shí)虎韵,若對(duì)象未被鎖定時(shí)易稠,或者當(dāng)前線程已經(jīng)擁有該對(duì)象的 monitor 鎖,則鎖計(jì)數(shù)器 +1包蓝,該線程獲取該對(duì)象鎖驶社。
  4. 當(dāng)執(zhí)行 monitorexit 時(shí),鎖計(jì)數(shù)器 -1测萎。當(dāng)計(jì)數(shù)器為 0 時(shí)亡电,此對(duì)象鎖就被釋放了。此時(shí)硅瞧,其它阻塞的線程可以請(qǐng)求獲取該 monitor 鎖份乒。
  5. 如果獲取 monitor 對(duì)象失敗,該線程則會(huì)進(jìn)入阻塞狀態(tài)腕唧,直到其他線程釋放鎖或辖。

2??方法級(jí)的同步隱式同步
方法級(jí)的同步是隱式,即無(wú)需通過(guò)字節(jié)碼指令來(lái)控制的枣接,它實(shí)現(xiàn)在方法調(diào)用和返回操作之中孝凌。JVM 可以從方法常量池中的方法表結(jié)構(gòu)(method_info Structure)中的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志區(qū)分一個(gè)方法是否同步方法。當(dāng)方法調(diào)用時(shí)月腋,調(diào)用指令將會(huì)檢查方法的 ACC_SYNCHRONIZED 訪問(wèn)標(biāo)志,如果設(shè)置了瓣赂,執(zhí)行線程將先持有 monitor榆骚,然后再執(zhí)行方法,最后在方法(正常/非正常)完成時(shí)釋放 monitor煌集。

3??關(guān)于 monitorenter/monitorexit妓肢、ACC_SYNCHRONIZED 指令,可以看下下面的反編譯代碼:

public class SynchronizedDemo {
    public void explicit() {
        synchronized (this) {//這個(gè)是同步代碼塊
            System.out.println("this method is explicit");
        }
    }
    public synchronized void implicit() {//這個(gè)是同步方法
        System.out.println("this method is implicit");
    }
    public static void main(String[] args) {
    }
}

切換到目標(biāo)類目錄執(zhí)行javac SynchronizedDemo.java命令生成編譯后的 .class 文件苫纤。執(zhí)行javap -c -s -v -l SynchronizedDemojavap -verbose SynchronizedDemo反編譯后得到:

同步代碼塊反編譯后得到monitorenter和monitorexit指令
同步方法碉钠,反編譯后得到ACC_SYNCHRONIZED標(biāo)志

tips:通過(guò)javap SynchronizedDemo可以查看其中的內(nèi)容纲缓。

三、JVM 對(duì) synchronized 的鎖優(yōu)化

Java6 為了減少獲得鎖和釋放鎖帶來(lái)的性能消耗喊废,引入了“偏向鎖”和“輕量級(jí)鎖”祝高。鎖的狀態(tài)總共有四種,級(jí)別從低到高依次是:無(wú)鎖狀態(tài)污筷、偏向鎖工闺、輕量級(jí)鎖和重量級(jí)鎖。隨著鎖的競(jìng)爭(zhēng)瓣蛀,鎖可以升級(jí)但不能降級(jí)陆蟆。

1??偏向鎖
偏向鎖是 Java6 之后加入的新鎖,它是一種針對(duì)加鎖操作的優(yōu)化手段惋增,目的是消除數(shù)據(jù)在無(wú)競(jìng)爭(zhēng)情況下的同步原語(yǔ)叠殷,進(jìn)一步提高程序的性能。通常诈皿,鎖不僅不存在多線程競(jìng)爭(zhēng)林束,而且還總是由同一線程多次獲得,為了減少同一線程獲取鎖(涉及到 CAS 操作纫塌,耗時(shí))的代價(jià)而引入偏向鎖诊县。偏向鎖的核心思想是,如果一個(gè)線程獲得了鎖措左,那么鎖就進(jìn)入偏向模式依痊,此時(shí) Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當(dāng)該線程再次請(qǐng)求鎖時(shí)怎披,無(wú)需再做任何同步操作胸嘁,即獲取鎖的過(guò)程,這樣就省去了大量有關(guān)鎖申請(qǐng)的操作凉逛,從而也就提升程序的性能性宏。所以,對(duì)于沒(méi)有鎖競(jìng)爭(zhēng)的場(chǎng)合状飞,偏向鎖有很好的優(yōu)化效果毫胜,畢竟極有可能連續(xù)多次是同一個(gè)線程申請(qǐng)相同的鎖。但是對(duì)于鎖競(jìng)爭(zhēng)比較激烈的場(chǎng)合诬辈,偏向鎖就失效了酵使,因?yàn)檫@樣場(chǎng)合每次申請(qǐng)鎖的線程極有可能都是不相同的,使用偏向鎖得不償失焙糟。注意口渔,偏向鎖失敗后,并不會(huì)立即膨脹為重量級(jí)鎖穿撮,而是先升級(jí)為輕量級(jí)鎖缺脉。

偏向鎖在 JDK8 中痪欲,默認(rèn)是輕量級(jí)鎖。但如果設(shè)定了-XX:BiasedLockingStartupDelay=0攻礼,那在對(duì)一個(gè) Object 做 synchronized 的時(shí)候业踢,會(huì)立即上一把偏向鎖。當(dāng)處于偏向鎖狀態(tài)時(shí)秘蛔,markwork 會(huì)記錄當(dāng)前線程 ID陨亡。

  1. 判斷是否為可偏向狀態(tài)。
  2. 如果為可偏向狀態(tài)深员,則判斷線程 ID 是否是當(dāng)前線程负蠕,如果是進(jìn)入同步塊。
  3. 如果線程 ID 并未指向當(dāng)前線程倦畅,利用 CAS 操作競(jìng)爭(zhēng)鎖遮糖,如果競(jìng)爭(zhēng)成功,將 Mark Word 中線程 ID 更新為當(dāng)前線程 ID叠赐,進(jìn)入同步塊欲账。
  4. 如果競(jìng)爭(zhēng)失敗,等待全局安全點(diǎn)芭概,準(zhǔn)備撤銷偏向鎖赛不,根據(jù)線程是否處于活動(dòng)狀態(tài),決定是轉(zhuǎn)換為無(wú)鎖狀態(tài)還是升級(jí)為輕量級(jí)鎖罢洲。

當(dāng)鎖對(duì)象第一次被線程獲取的時(shí)候踢故,虛擬機(jī)會(huì)把對(duì)象頭中的標(biāo)志位設(shè)置為“01”,即偏向模式惹苗。同時(shí)使用 CAS 操作把獲取到這個(gè)鎖的線程 ID 記錄在對(duì)象的 Mark Word 中殿较。如果 CAS 操作成功,持有偏向鎖的線程以后每次進(jìn)入這個(gè)鎖相關(guān)的同步塊時(shí)桩蓉,虛擬機(jī)都可以不再進(jìn)行任何同步操作淋纲。

偏向鎖的釋放:

偏向鎖使用了遇到競(jìng)爭(zhēng)才釋放鎖的機(jī)制。偏向鎖的撤銷需要等待全局安全點(diǎn)院究,然后它會(huì)首先暫停擁有偏向鎖的線程洽瞬,然后判斷線程是否還活著,如果線程還活著业汰,則升級(jí)為輕量級(jí)鎖伙窃,否則,將鎖設(shè)置為無(wú)鎖狀態(tài)蔬胯。

2??輕量級(jí)鎖
當(dāng)下一個(gè)線程參與到偏向鎖競(jìng)爭(zhēng)時(shí),會(huì)先判斷 markword 中保存的線程 ID 是否與這個(gè)線程 ID 相等位他,如果不相等氛濒,會(huì)立即撤銷偏向鎖产场,升級(jí)為輕量級(jí)鎖。每個(gè)線程在自己的線程棧中生成一個(gè) LockRecord(LR)舞竿,然后每個(gè)線程通過(guò) CAS(自旋) 的操作將鎖對(duì)象頭中的 markwork 設(shè)置為指向自己的 LR 的指針京景,哪個(gè)線程設(shè)置成功,就意味著獲得鎖骗奖。關(guān)于 synchronized 中此時(shí)執(zhí)行的 CAS 操作是通過(guò) native 的調(diào)用 HotSpot 中 bytecodeInterpreter.cpp 文件 C++ 代碼實(shí)現(xiàn)的确徙。

倘若偏向鎖失敗,虛擬機(jī)并不會(huì)立即升級(jí)為重量級(jí)鎖执桌,它還會(huì)嘗試使用一種稱為輕量級(jí)鎖的優(yōu)化手段(1.6 之后加入的)鄙皇,此時(shí) Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級(jí)鎖的結(jié)構(gòu)。輕量級(jí)鎖能夠提升程序性能的依據(jù)是“對(duì)絕大部分的鎖仰挣,在整個(gè)同步周期內(nèi)都不存在競(jìng)爭(zhēng)”伴逸,注意這是經(jīng)驗(yàn)數(shù)據(jù)。需要了解的是膘壶,輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的場(chǎng)合错蝴,如果存在同一時(shí)間訪問(wèn)同一鎖的場(chǎng)合,就會(huì)導(dǎo)致輕量級(jí)鎖膨脹為重量級(jí)鎖颓芭。

3??自旋鎖
輕量級(jí)鎖失敗后顷锰,虛擬機(jī)為了避免線程真實(shí)地在操作系統(tǒng)層面掛起,還會(huì)進(jìn)行一項(xiàng)稱為自旋鎖的優(yōu)化手段亡问。這是基于在大多數(shù)情況下官紫,線程持有鎖的時(shí)間都不會(huì)太長(zhǎng),如果直接掛起操作系統(tǒng)層面的線程可能會(huì)得不償失玛界,畢竟操作系統(tǒng)實(shí)現(xiàn)線程之間的切換時(shí)需要從用戶態(tài)轉(zhuǎn)換到核心態(tài)万矾,這個(gè)狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,時(shí)間成本相對(duì)較高慎框,因此自旋鎖會(huì)假設(shè)在不久將來(lái)良狈,當(dāng)前的線程可以獲得鎖,因此虛擬機(jī)會(huì)讓當(dāng)前想要獲取鎖的線程做幾個(gè)空循環(huán)(這也是稱為自旋的原因)笨枯,一般不會(huì)太久薪丁,可能是 50 或 100 個(gè)循環(huán),經(jīng)過(guò)若干次循環(huán)后馅精,如果得到鎖就順利進(jìn)入臨界區(qū)严嗜。如果還不能獲得鎖,那就會(huì)將線程在操作系統(tǒng)層面掛起洲敢,這就是自旋鎖的優(yōu)化方式漫玄,這種方式確實(shí)也是可以提升效率的。最后沒(méi)辦法也就只能升級(jí)為重量級(jí)鎖了。

4??重量級(jí)鎖
如果鎖競(jìng)爭(zhēng)加劇(如線程自旋次數(shù)或者自旋的線程數(shù)超過(guò)某閾值睦优,JDK1.6 之后渗常,由 JVM 自己控制改規(guī)則),就會(huì)升級(jí)為重量級(jí)鎖汗盘。此時(shí)就會(huì)向操作系統(tǒng)申請(qǐng)資源皱碘,線程掛起,進(jìn)入到操作系統(tǒng)內(nèi)核態(tài)的等待隊(duì)列中隐孽,等待操作系統(tǒng)調(diào)度癌椿,然后映射回用戶態(tài)。在重量級(jí)鎖中菱阵,由于需要做內(nèi)核態(tài)到用戶態(tài)的轉(zhuǎn)換踢俄,而這個(gè)過(guò)程中需要消耗較多時(shí)間,也就是“重”的原因之一送粱。

Synchronized 的重量級(jí)鎖是通過(guò)對(duì)象內(nèi)部的一個(gè)叫做監(jiān)視器鎖(monitor)來(lái)實(shí)現(xiàn)的褪贵,監(jiān)視器鎖本質(zhì)又是依賴于底層的操作系統(tǒng)的 Mutex Lock(互斥鎖) 來(lái)實(shí)現(xiàn)的。而操作系統(tǒng)實(shí)現(xiàn)線程之間的切換需要從用戶態(tài)轉(zhuǎn)換到核心態(tài)抗俄,這個(gè)成本非常高脆丁,狀態(tài)之間的轉(zhuǎn)換需要相對(duì)比較長(zhǎng)的時(shí)間,這就是為什么 Synchronized 效率低的原因动雹。因此槽卫,這種依賴于操作系統(tǒng) Mutex Lock 所實(shí)現(xiàn)的鎖稱之為“重量級(jí)鎖”。

5??鎖消除
鎖消除是虛擬機(jī)另外一種鎖的優(yōu)化胰蝠,這種優(yōu)化更徹底歼培,Java 虛擬機(jī)在 JIT 編譯時(shí)(可以簡(jiǎn)單理解為當(dāng)某段代碼即將第一次被執(zhí)行時(shí)進(jìn)行編譯,又稱即時(shí)編譯)茸塞,通過(guò)對(duì)運(yùn)行上下文的掃描躲庄,去除不可能存在共享資源競(jìng)爭(zhēng)的鎖,通過(guò)這種方式消除沒(méi)有必要的鎖钾虐,可以節(jié)省毫無(wú)意義的請(qǐng)求鎖時(shí)間噪窘。如:

public void add() {
    StringBuffer sb = new StringBuffer();
    sb.append("a").append("b");
}

StringBuffer 的 append() 是一個(gè)同步方法。代碼中 add() 中的局部對(duì)象 sb效扫,只在該方法內(nèi)的作用域有效倔监,不可能被其他線程引用(因?yàn)槭蔷植孔兞浚瑮K接?菌仁。不同線程同時(shí)調(diào)用 add() 時(shí)浩习,都會(huì)創(chuàng)建不同的 sb 對(duì)象,sb 不可能存在共享資源競(jìng)爭(zhēng)的情景济丘。因此此時(shí)的 append() 若是同步谱秽,就是白白浪費(fèi)的系統(tǒng)資源。JVM 會(huì)自動(dòng)消除 StringBuffer 對(duì)象內(nèi)部的鎖。

6??鎖粗化
如果虛擬機(jī)檢測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖疟赊,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部辱士。如:

public String test() {
    int i = 0;
    StringBuffer sb = new StringBuffer();
    while (i < 100) {
        sb.append("a");
        i++;
    }
    return sb.toString();
}

JVM 會(huì)檢測(cè)到這樣一連串的操作都對(duì)同一個(gè)對(duì)象加鎖(while 循環(huán)內(nèi) 100 次執(zhí)行 append(),沒(méi)有鎖粗化就要進(jìn)行 100 次加鎖/解鎖)听绳,此時(shí) JVM 就會(huì)將加鎖的范圍粗化到這一連串的操作的外部(比如 while 循環(huán)體外),使得這一連串操作只需要加一次鎖即可异赫。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椅挣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子塔拳,更是在濱河造成了極大的恐慌鼠证,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件靠抑,死亡現(xiàn)場(chǎng)離奇詭異量九,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)颂碧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門荠列,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人载城,你說(shuō)我怎么就攤上這事肌似。” “怎么了诉瓦?”我有些...
    開(kāi)封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵川队,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我睬澡,道長(zhǎng)固额,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任煞聪,我火速辦了婚禮斗躏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘米绕。我一直安慰自己瑟捣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布栅干。 她就那樣靜靜地躺著迈套,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碱鳞。 梳的紋絲不亂的頭發(fā)上桑李,一...
    開(kāi)封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼贵白。 笑死率拒,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的禁荒。 我是一名探鬼主播猬膨,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼呛伴!你這毒婦竟也來(lái)了勃痴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤热康,失蹤者是張志新(化名)和其女友劉穎沛申,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體姐军,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡铁材,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奕锌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片著觉。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惊暴,靈堂內(nèi)的尸體忽然破棺而出固惯,到底是詐尸還是另有隱情,我是刑警寧澤缴守,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布葬毫,位于F島的核電站,受9級(jí)特大地震影響屡穗,放射性物質(zhì)發(fā)生泄漏贴捡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一村砂、第九天 我趴在偏房一處隱蔽的房頂上張望烂斋。 院中可真熱鬧,春花似錦础废、人聲如沸汛骂。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)帘瞭。三九已至,卻和暖如春蒿讥,著一層夾襖步出監(jiān)牢的瞬間蝶念,已是汗流浹背抛腕。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留媒殉,地道東北人担敌。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像廷蓉,于是被迫代替她去往敵國(guó)和親全封。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353